Skip to content

Commit f3d08c9

Browse files
authored
feat: structured logging (#43)
1 parent 9b96586 commit f3d08c9

File tree

10 files changed

+119
-119
lines changed

10 files changed

+119
-119
lines changed

DEVELOPMENT.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,18 @@ kind create cluster --config kind-poc-cluster.yaml
3939
kind load docker-image --name poc kube-startup-cpu-boost:dev
4040
```
4141

42-
4. Deploy controller on a cluster
42+
4. Enable development logging and other options if needed
43+
44+
In `config/default/kustomization.yaml` uncomment the dev patch:
45+
46+
```yaml
47+
# Uncomment below for development
48+
- manager_config_dev_patch.yaml
49+
```
50+
51+
Adapt the `config/default/manager_config_dev_patch.yaml` if needed.
52+
53+
5. Deploy controller on a cluster
4354

4455
```sh
4556
make deploy IMG=docker.io/library/kube-startup-cpu-boost:dev

cmd/main.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,14 @@ import (
2020

2121
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
2222
// to ensure that exec-entrypoint and run can make use of them.
23-
"go.uber.org/zap"
24-
"go.uber.org/zap/zapcore"
23+
2524
_ "k8s.io/client-go/plugin/pkg/client/auth"
2625

2726
"k8s.io/apimachinery/pkg/runtime"
2827
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
2928
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
3029
ctrl "sigs.k8s.io/controller-runtime"
3130
"sigs.k8s.io/controller-runtime/pkg/healthz"
32-
ctrlZap "sigs.k8s.io/controller-runtime/pkg/log/zap"
3331
"sigs.k8s.io/controller-runtime/pkg/webhook"
3432

3533
autoscalingv1alpha1 "github.com/google/kube-startup-cpu-boost/api/v1alpha1"
@@ -61,11 +59,7 @@ func main() {
6159
setupLog.Error(err, "unable to load configuration")
6260
os.Exit(1)
6361
}
64-
ctrlZapOpts := ctrlZap.Options{
65-
Development: true,
66-
Level: zap.NewAtomicLevelAt(zapcore.Level(cfg.ZapLogLevel)),
67-
}
68-
ctrl.SetLogger(ctrlZap.New(ctrlZap.UseFlagOptions(&ctrlZapOpts)))
62+
ctrl.SetLogger(config.Logger(cfg.ZapDevelopment, cfg.ZapLogLevel))
6963
tlsOpts := []func(*tls.Config){}
7064

7165
webhookServer := webhook.NewServer(webhook.Options{

config/default/kustomization.yaml

Lines changed: 3 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,8 @@ resources:
1919
- ../rbac
2020
- ../manager
2121
- ../internalcert
22-
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
23-
# crd/kustomization.yaml
2422
- ../webhook
25-
# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required.
26-
#- ../certmanager
23+
2724
# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.
2825
#- ../prometheus
2926

@@ -39,107 +36,5 @@ patchesStrategicMerge:
3936
# crd/kustomization.yaml
4037
- manager_webhook_patch.yaml
4138

42-
# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'.
43-
# Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks.
44-
# 'CERTMANAGER' needs to be enabled to use ca injection
45-
#- webhookcainjection_patch.yaml
46-
47-
# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.
48-
# Uncomment the following replacements to add the cert-manager CA injection annotations
49-
#replacements:
50-
# - source: # Add cert-manager annotation to ValidatingWebhookConfiguration, MutatingWebhookConfiguration and CRDs
51-
# kind: Certificate
52-
# group: cert-manager.io
53-
# version: v1
54-
# name: serving-cert # this name should match the one in certificate.yaml
55-
# fieldPath: .metadata.namespace # namespace of the certificate CR
56-
# targets:
57-
# - select:
58-
# kind: ValidatingWebhookConfiguration
59-
# fieldPaths:
60-
# - .metadata.annotations.[cert-manager.io/inject-ca-from]
61-
# options:
62-
# delimiter: '/'
63-
# index: 0
64-
# create: true
65-
# - select:
66-
# kind: MutatingWebhookConfiguration
67-
# fieldPaths:
68-
# - .metadata.annotations.[cert-manager.io/inject-ca-from]
69-
# options:
70-
# delimiter: '/'
71-
# index: 0
72-
# create: true
73-
# - select:
74-
# kind: CustomResourceDefinition
75-
# fieldPaths:
76-
# - .metadata.annotations.[cert-manager.io/inject-ca-from]
77-
# options:
78-
# delimiter: '/'
79-
# index: 0
80-
# create: true
81-
# - source:
82-
# kind: Certificate
83-
# group: cert-manager.io
84-
# version: v1
85-
# name: serving-cert # this name should match the one in certificate.yaml
86-
# fieldPath: .metadata.name
87-
# targets:
88-
# - select:
89-
# kind: ValidatingWebhookConfiguration
90-
# fieldPaths:
91-
# - .metadata.annotations.[cert-manager.io/inject-ca-from]
92-
# options:
93-
# delimiter: '/'
94-
# index: 1
95-
# create: true
96-
# - select:
97-
# kind: MutatingWebhookConfiguration
98-
# fieldPaths:
99-
# - .metadata.annotations.[cert-manager.io/inject-ca-from]
100-
# options:
101-
# delimiter: '/'
102-
# index: 1
103-
# create: true
104-
# - select:
105-
# kind: CustomResourceDefinition
106-
# fieldPaths:
107-
# - .metadata.annotations.[cert-manager.io/inject-ca-from]
108-
# options:
109-
# delimiter: '/'
110-
# index: 1
111-
# create: true
112-
# - source: # Add cert-manager annotation to the webhook Service
113-
# kind: Service
114-
# version: v1
115-
# name: webhook-service
116-
# fieldPath: .metadata.name # namespace of the service
117-
# targets:
118-
# - select:
119-
# kind: Certificate
120-
# group: cert-manager.io
121-
# version: v1
122-
# fieldPaths:
123-
# - .spec.dnsNames.0
124-
# - .spec.dnsNames.1
125-
# options:
126-
# delimiter: '.'
127-
# index: 0
128-
# create: true
129-
# - source:
130-
# kind: Service
131-
# version: v1
132-
# name: webhook-service
133-
# fieldPath: .metadata.namespace # namespace of the service
134-
# targets:
135-
# - select:
136-
# kind: Certificate
137-
# group: cert-manager.io
138-
# version: v1
139-
# fieldPaths:
140-
# - .spec.dnsNames.0
141-
# - .spec.dnsNames.1
142-
# options:
143-
# delimiter: '.'
144-
# index: 1
145-
# create: true
39+
# Uncomment below for development
40+
#- manager_config_dev_patch.yaml
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: controller-manager
5+
namespace: system
6+
spec:
7+
template:
8+
spec:
9+
containers:
10+
- name: manager
11+
env:
12+
- name: ZAP_DEVELOPMENT
13+
value: "true"
14+
- name: ZAP_LOG_LEVEL
15+
value: "-5"

config/default/manager_config_patch.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ spec:
1313
valueFrom:
1414
fieldRef:
1515
fieldPath: metadata.namespace
16-
- name: ZAP_LOG_LEVEL
17-
value: "-5"
1816
- name: "LEADER_ELECTION"
1917
value: "true"
2018
- name: METRICS_PROBE_BIND_ADDR

internal/config/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const (
2323
HealthProbeBindAddrDefault = ":8081"
2424
SecureMetricsDefault = false
2525
ZapLogLevelDefault = 0 // zapcore.InfoLevel
26+
ZapDevelopmentDefault = false
2627
)
2728

2829
// ConfigProvider provides the Kube Startup CPU Boost configuration
@@ -48,6 +49,8 @@ type Config struct {
4849
SecureMetrics bool
4950
// ZapLogLevel determines the log level for the ZAP logger
5051
ZapLogLevel int
52+
// ZapDevelopment determines if the ZAP logger is in development mode
53+
ZapDevelopment bool
5154
}
5255

5356
// LoadDefaults loads the default configuration values
@@ -59,4 +62,5 @@ func (c *Config) LoadDefaults() {
5962
c.HealthProbeBindAddr = HealthProbeBindAddrDefault
6063
c.SecureMetrics = SecureMetricsDefault
6164
c.ZapLogLevel = ZapLogLevelDefault
65+
c.ZapDevelopment = ZapDevelopmentDefault
6266
}

internal/config/config_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,8 @@ var _ = Describe("Config", func() {
5050
It("has valid ZAP log level", func() {
5151
Expect(cfg.ZapLogLevel).To(Equal(config.ZapLogLevelDefault))
5252
})
53+
It("has valid ZAP development ", func() {
54+
Expect(cfg.ZapDevelopment).To(Equal(config.ZapDevelopmentDefault))
55+
})
5356
})
5457
})

internal/config/env_provider.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const (
2828
HealthProbeBindAddrEnvVar = "HEALTH_PROBE_BIND_ADDR"
2929
SecureMetricsEnvVar = "SECURE_METRICS"
3030
ZapLogLevelEnvVar = "ZAP_LOG_LEVEL"
31+
ZapDevelopmentEnvVar = "ZAP_DEVELOPMENT"
3132
)
3233

3334
type LookupEnvFunc func(key string) (string, bool)
@@ -91,6 +92,13 @@ func (p *EnvConfigProvider) LoadConfig() (*Config, error) {
9192
errs = append(errs, fmt.Errorf("%s value is not an int: %s", ZapLogLevelEnvVar, err))
9293
}
9394
}
95+
if v, ok := p.lookupFunc(ZapDevelopmentEnvVar); ok {
96+
boolVal, err := strconv.ParseBool(v)
97+
config.ZapDevelopment = boolVal
98+
if err != nil {
99+
errs = append(errs, fmt.Errorf("%s value is not a bool: %s", ZapDevelopmentEnvVar, err))
100+
}
101+
}
94102
var err error
95103
if len(errs) > 0 {
96104
err = errors.Join(errs...)

internal/config/env_provider_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,5 +115,13 @@ var _ = Describe("EnvProvider", func() {
115115
Expect(cfg.ZapLogLevel).To(Equal(logLevel))
116116
})
117117
})
118+
When("zap development variable is set", func() {
119+
BeforeEach(func() {
120+
lookupFuncMap[config.ZapDevelopmentEnvVar] = "true"
121+
})
122+
It("has valid zap development", func() {
123+
Expect(cfg.ZapDevelopment).To(BeTrue())
124+
})
125+
})
118126
})
119127
})

internal/config/logger.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package config
16+
17+
import (
18+
"github.com/go-logr/logr"
19+
"go.uber.org/zap"
20+
"go.uber.org/zap/zapcore"
21+
ctrlZap "sigs.k8s.io/controller-runtime/pkg/log/zap"
22+
)
23+
24+
func Logger(development bool, level int) logr.Logger {
25+
ctrlZapOpts := ctrlZap.Options{
26+
Development: true,
27+
Level: zap.NewAtomicLevelAt(zapcore.Level(level)),
28+
}
29+
if !development {
30+
ctrlZapOpts.Development = false
31+
ctrlZapOpts.EncoderConfigOptions = []ctrlZap.EncoderConfigOption{
32+
logEncoderOptionsProd(),
33+
}
34+
}
35+
return ctrlZap.New(ctrlZap.UseFlagOptions(&ctrlZapOpts))
36+
}
37+
38+
// logEncoderOptionsProd
39+
func logEncoderOptionsProd() ctrlZap.EncoderConfigOption {
40+
return func(ec *zapcore.EncoderConfig) {
41+
ec.LevelKey = "severity"
42+
ec.MessageKey = "message"
43+
ec.TimeKey = "time"
44+
ec.EncodeTime = zapcore.RFC3339TimeEncoder
45+
ec.EncodeLevel = func(l zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {
46+
switch l {
47+
case zapcore.InfoLevel:
48+
enc.AppendString("info")
49+
case zapcore.WarnLevel:
50+
enc.AppendString("warning")
51+
case zapcore.ErrorLevel:
52+
enc.AppendString("error")
53+
case zapcore.DPanicLevel:
54+
enc.AppendString("critical")
55+
case zapcore.PanicLevel:
56+
enc.AppendString("alert")
57+
case zapcore.FatalLevel:
58+
enc.AppendString("emergency")
59+
default:
60+
enc.AppendString("debug")
61+
}
62+
}
63+
}
64+
}

0 commit comments

Comments
 (0)