Skip to content
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
26 changes: 25 additions & 1 deletion pkg/utils/logger/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,35 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log/zap"
)

// severityLevelEncoder encodes log levels to cloud-compatible severity names.
// Uses "WARNING" instead of zap's "WARN", and maps DPANIC/PANIC/FATAL to CRITICAL.
func severityLevelEncoder(level zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {
switch level {
case zapcore.WarnLevel:
enc.AppendString("WARNING")
case zapcore.DPanicLevel, zapcore.PanicLevel, zapcore.FatalLevel:
enc.AppendString("CRITICAL")
default:
enc.AppendString(level.CapitalString())
}
}

func NewLogger() logr.Logger {
opts := zap.Options{
Development: true,
// Use production mode for JSON output (enables structured logging in cloud environments).
// Can be overridden with --zap-devel flag for local development.
Development: false,
StacktraceLevel: zapcore.DPanicLevel,
TimeEncoder: zapcore.RFC3339TimeEncoder,
// Configure encoder for cloud logging compatibility:
// - Use "severity" field name (recognized by GKE, AKS, EKS log explorers)
// - Use standard severity names (WARNING instead of WARN, CRITICAL for fatal errors)
EncoderConfigOptions: []zap.EncoderConfigOption{
func(ec *zapcore.EncoderConfig) {
ec.LevelKey = "severity"
ec.EncodeLevel = severityLevelEncoder
},
},
}
return zap.New(zap.UseFlagOptions(&opts))
}
70 changes: 70 additions & 0 deletions pkg/utils/logger/logger_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright Mondoo, Inc. 2026
// SPDX-License-Identifier: BUSL-1.1

package logger

import (
"testing"

"github.com/stretchr/testify/assert"
"go.uber.org/zap/zapcore"
)

type testEncoder struct {
result string
}

func (t *testEncoder) AppendString(s string) {
t.result = s
}

// Implement remaining PrimitiveArrayEncoder methods as no-ops
func (t *testEncoder) AppendBool(bool) {}
func (t *testEncoder) AppendByteString([]byte) {}
func (t *testEncoder) AppendComplex128(complex128) {}
func (t *testEncoder) AppendComplex64(complex64) {}
func (t *testEncoder) AppendFloat64(float64) {}
func (t *testEncoder) AppendFloat32(float32) {}
func (t *testEncoder) AppendInt(int) {}
func (t *testEncoder) AppendInt64(int64) {}
func (t *testEncoder) AppendInt32(int32) {}
func (t *testEncoder) AppendInt16(int16) {}
func (t *testEncoder) AppendInt8(int8) {}
func (t *testEncoder) AppendUint(uint) {}
func (t *testEncoder) AppendUint64(uint64) {}
func (t *testEncoder) AppendUint32(uint32) {}
func (t *testEncoder) AppendUint16(uint16) {}
func (t *testEncoder) AppendUint8(uint8) {}
func (t *testEncoder) AppendUintptr(uintptr) {}

func TestSeverityLevelEncoder(t *testing.T) {
tests := []struct {
level zapcore.Level
expected string
}{
{zapcore.DebugLevel, "DEBUG"},
{zapcore.InfoLevel, "INFO"},
{zapcore.WarnLevel, "WARNING"}, // Cloud log explorers expect WARNING, not WARN
{zapcore.ErrorLevel, "ERROR"},
{zapcore.DPanicLevel, "CRITICAL"},
{zapcore.PanicLevel, "CRITICAL"},
{zapcore.FatalLevel, "CRITICAL"},
}

for _, tt := range tests {
t.Run(tt.level.String(), func(t *testing.T) {
enc := &testEncoder{}
severityLevelEncoder(tt.level, enc)
assert.Equal(t, tt.expected, enc.result)
})
}
}

func TestNewLogger(t *testing.T) {
// Ensure NewLogger creates a valid logger without panicking
logger := NewLogger()
assert.NotNil(t, logger)

// Test that the logger can be used
logger.Info("test message", "key", "value")
}
Loading