Skip to content

Commit 09d9cdb

Browse files
fix: add log level support & decrease info level verbosity
1 parent ffcd8f4 commit 09d9cdb

File tree

10 files changed

+212
-54
lines changed

10 files changed

+212
-54
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ bin
66

77
# Release toolkit
88
CHANGELOG.partial.md
9+
10+
.idea

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
### 🐞 Bug fixes
11+
- Add log level support & decrease info level verbosity @michaelprice232 [#701](https://github.com/newrelic/k8s-metadata-injection/pull/701)
12+
1013
## v1.41.0 - 2026-02-02
1114

1215
### 🚀 Enhancements

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,9 @@ To run the webhook locally with Minikube:
8383
# Configure shell to use Minikube's Docker daemon
8484
eval $(minikube docker-env)
8585

86-
# Build the image
87-
make compile build-container DOCKER_IMAGE_TAG=local-dev
86+
# Build the image
87+
# Update the GOOS and GOARCH envars based on your system (https://docs.docker.com/build/building/multi-platform/)
88+
make compile build-container DOCKER_IMAGE_TAG=local-dev GOOS=linux GOARCH=arm64
8889
```
8990

9091
3. **Install the webhook using Helm**:

charts/nri-metadata-injection/README.md

Lines changed: 42 additions & 41 deletions
Large diffs are not rendered by default.

charts/nri-metadata-injection/templates/deployment.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ spec:
5656
value: {{ .Values.ports.webhook | quote }}
5757
- name: NEW_RELIC_K8S_METADATA_INJECTION_HEALTH_PORT
5858
value: {{ .Values.ports.health | quote }}
59+
- name: NEW_RELIC_K8S_METADATA_INJECTION_LOG_LEVEL
60+
value: {{ .Values.logLevel | quote }}
5961
ports:
6062
- containerPort: {{ .Values.ports.webhook }}
6163
protocol: TCP
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
suite: test log level configuration
2+
templates:
3+
- templates/deployment.yaml
4+
release:
5+
name: release
6+
namespace: ns
7+
tests:
8+
- it: should use default log level when not specified
9+
set:
10+
cluster: test-cluster
11+
asserts:
12+
- contains:
13+
path: spec.template.spec.containers[0].env
14+
content:
15+
name: NEW_RELIC_K8S_METADATA_INJECTION_LOG_LEVEL
16+
value: "info"
17+
template: templates/deployment.yaml
18+
19+
- it: should use correct log level when specified
20+
set:
21+
cluster: test-cluster
22+
logLevel: debug
23+
asserts:
24+
- contains:
25+
path: spec.template.spec.containers[0].env
26+
content:
27+
name: NEW_RELIC_K8S_METADATA_INJECTION_LOG_LEVEL
28+
value: "debug"
29+
template: templates/deployment.yaml

charts/nri-metadata-injection/values.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ cluster: ""
99
# -- The provider that you are deploying your cluster on. Sets config options providers that are known to have constraints.
1010
provider:
1111

12+
# -- Log level for the app (e.g., debug, info, warn, error)
13+
logLevel: info
14+
1215
# -- Image for the New Relic Metadata Injector
1316
# @default -- See `values.yaml`
1417
image:

cmd/server/main.go

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ type specification struct {
3333
TLSKeyFile string `default:"/etc/tls-key-cert-pair/tls.key" envconfig:"tls_key_file"` // File containing the x509 private key for TLSCERTFILE.
3434
ClusterName string `default:"cluster" split_words:"true"` // The name of the Kubernetes cluster.
3535
Timeout time.Duration `default:"1s"` // Server timeout for the pod mutation.
36+
LogLevel string `default:"info" split_words:"true"` // Logger log level.
3637
}
3738

3839
func main() {
@@ -42,7 +43,7 @@ func main() {
4243
log.Fatal(err.Error())
4344
}
4445

45-
logger := setupLogger()
46+
logger := setupLogger(s.LogLevel)
4647
defer func() { _ = logger.Sync() }()
4748

4849
pair, err := tls.LoadX509KeyPair(s.TLSCertFile, s.TLSKeyFile)
@@ -144,7 +145,7 @@ func withLoggingMiddleware(logger *zap.SugaredLogger) func(next http.Handler) ht
144145
if r.TLS != nil {
145146
scheme = "https"
146147
}
147-
logger.Infof("%s %s://%s%s %s\" from %s", r.Method, scheme, r.Host, r.RequestURI, r.Proto, r.RemoteAddr)
148+
logger.Debugf("%s %s://%s%s %s\" from %s", r.Method, scheme, r.Host, r.RequestURI, r.Proto, r.RemoteAddr)
148149

149150
next.ServeHTTP(w, r)
150151
}
@@ -153,13 +154,33 @@ func withLoggingMiddleware(logger *zap.SugaredLogger) func(next http.Handler) ht
153154
}
154155
}
155156

156-
func setupLogger() *zap.SugaredLogger {
157+
func setupLogger(lvl string) *zap.SugaredLogger {
158+
level := strings.ToLower(strings.TrimSpace(lvl))
159+
atom := zap.NewAtomicLevel()
160+
invalidLevel := false
161+
if err := atom.UnmarshalText([]byte(level)); err != nil {
162+
atom.SetLevel(zap.InfoLevel)
163+
invalidLevel = true
164+
}
165+
157166
config := zap.NewProductionConfig()
158-
config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder // We want human readable timestamps.
167+
config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder // We want human-readable timestamps.
168+
config.Level = atom
159169

160170
zapLogger, err := config.Build()
161171
if err != nil {
162172
log.Fatalf("can't initialize zap logger: %v", err)
163173
}
164-
return zapLogger.Sugar()
174+
175+
logger := zapLogger.Sugar()
176+
177+
if invalidLevel {
178+
logger.Warnw(
179+
"invalid log level, defaulting to info",
180+
"provided", level,
181+
"default", "info",
182+
)
183+
}
184+
185+
return logger
165186
}

cmd/server/main_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package main
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"go.uber.org/zap/zapcore"
8+
)
9+
10+
func TestSetupLogger(t *testing.T) {
11+
t.Parallel()
12+
13+
cases := []struct {
14+
name string
15+
level string
16+
expectedLevel zapcore.Level
17+
shouldWarn bool
18+
}{
19+
{
20+
name: "debug level",
21+
level: "debug",
22+
expectedLevel: zapcore.DebugLevel,
23+
shouldWarn: false,
24+
},
25+
{
26+
name: "info level",
27+
level: "info",
28+
expectedLevel: zapcore.InfoLevel,
29+
shouldWarn: false,
30+
},
31+
{
32+
name: "warn level",
33+
level: "warn",
34+
expectedLevel: zapcore.WarnLevel,
35+
shouldWarn: false,
36+
},
37+
{
38+
name: "error level",
39+
level: "error",
40+
expectedLevel: zapcore.ErrorLevel,
41+
shouldWarn: false,
42+
},
43+
{
44+
name: "uppercase level",
45+
level: "INFO",
46+
expectedLevel: zapcore.InfoLevel,
47+
shouldWarn: false,
48+
},
49+
{
50+
name: "level with whitespace",
51+
level: " warn ",
52+
expectedLevel: zapcore.WarnLevel,
53+
shouldWarn: false,
54+
},
55+
{
56+
name: "invalid level defaults to info",
57+
level: "invalid",
58+
expectedLevel: zapcore.InfoLevel,
59+
shouldWarn: true,
60+
},
61+
{
62+
name: "empty level defaults to info",
63+
level: "",
64+
expectedLevel: zapcore.InfoLevel,
65+
shouldWarn: true,
66+
},
67+
}
68+
69+
for _, c := range cases {
70+
t.Run(c.name, func(t *testing.T) {
71+
t.Parallel()
72+
73+
logger := setupLogger(c.level)
74+
assert.NotNil(t, logger)
75+
76+
// Verify the logger's level
77+
core := logger.Desugar().Core()
78+
assert.True(t, core.Enabled(c.expectedLevel))
79+
80+
// For invalid levels, verify it still works at info level
81+
if c.shouldWarn {
82+
assert.True(t, core.Enabled(zapcore.InfoLevel))
83+
}
84+
})
85+
}
86+
}

src/server/webhook.go

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func (whsvr *Webhook) getEnvVarsToInject(pod *corev1.Pod, container *corev1.Cont
5050
createEnvVarFromString("NEW_RELIC_METADATA_KUBERNETES_CONTAINER_IMAGE_NAME", container.Image),
5151
}
5252

53-
whsvr.Logger.Infow("creating env variables", "cluster_name", whsvr.ClusterName, "container_name", container.Name, "container_image", container.Image)
53+
whsvr.Logger.Debugw("creating env variables", "cluster_name", whsvr.ClusterName, "container_name", container.Name, "container_image", container.Image)
5454
// Guess the name of the deployment. We check whether the Pod is Owned by a ReplicaSet and confirms with the
5555
// naming convention for a Deployment. This can give a false positive if the user uses ReplicaSets directly.
5656
if len(pod.OwnerReferences) == 1 && pod.OwnerReferences[0].Kind == "ReplicaSet" {
@@ -156,12 +156,22 @@ func (whsvr *Webhook) mutate(ar *admissionv1.AdmissionReview) ([]byte, error) {
156156
return nil, err
157157
}
158158

159-
whsvr.Logger.Infow("received admission review", "kind", req.Kind, "namespace", req.Namespace, "name",
159+
willMutate := mutationRequired(ignoredNamespaces, &pod.ObjectMeta)
160+
whsvr.Logger.Infow(
161+
"admission review received",
162+
"operation", req.Operation,
163+
"kind", req.Kind.Kind,
164+
"namespace", pod.Namespace,
165+
"podGenerateName", pod.GenerateName, // final pod name not set until after the admission webhooks have run
166+
"uid", req.UID,
167+
"mutate", willMutate,
168+
)
169+
170+
whsvr.Logger.Debugw("received admission review", "kind", req.Kind, "namespace", req.Namespace, "name",
160171
req.Name, "pod", pod.Name, "UID", req.UID, "operation", req.Operation, "userinfo", req.UserInfo)
161172

162173
// determine whether to perform mutation
163-
if !mutationRequired(ignoredNamespaces, &pod.ObjectMeta) {
164-
whsvr.Logger.Infow("skipped mutation", "namespace", pod.Namespace, "pod", pod.Name, "reason", "policy check (special namespaces)")
174+
if !willMutate {
165175
return nil, nil
166176
}
167177

@@ -170,7 +180,7 @@ func (whsvr *Webhook) mutate(ar *admissionv1.AdmissionReview) ([]byte, error) {
170180
return nil, err
171181
}
172182

173-
whsvr.Logger.Infow("admission response created", "response", string(patchBytes))
183+
whsvr.Logger.Debugw("admission response created", "response", string(patchBytes))
174184
return patchBytes, nil
175185
}
176186

@@ -259,7 +269,7 @@ func (whsvr *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) {
259269
http.Error(w, fmt.Sprintf("could not encode response: %v", err), http.StatusInternalServerError)
260270
return
261271
}
262-
whsvr.Logger.Info("writing response")
272+
whsvr.Logger.Debug("writing response")
263273
if _, err := w.Write(resp); err != nil {
264274
whsvr.Logger.Errorw("can't write response", "err", err)
265275
http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)

0 commit comments

Comments
 (0)