Skip to content

Commit feff81f

Browse files
chris-rockclaude
andauthored
⭐️ fix logging severity for GKE/cloud log explorers (#1372)
* ⭐️ fix logging severity for GKE/cloud log explorers Configure the logger to output structured JSON with GCP-compatible severity levels. This fixes issue #916 where both INFO and ERROR messages were incorrectly recognized as errors in Google Log Explorer. Changes: - Use JSON encoding by default (Development: false) for proper cloud log parsing - Use "severity" field instead of "level" for log level (GCP requirement) - Map WARN to WARNING (GCP expects WARNING, not WARN) - Map DPANIC/PANIC/FATAL to CRITICAL - Add tests for the GCP level encoder This change is also compatible with AKS and EKS log aggregation. Fixes #916 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Rename gcpLevelEncoder to severityLevelEncoder for cloud-agnostic naming Update comments to reference all major cloud providers (GKE, AKS, EKS) instead of just GCP/GKE. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 5808c7d commit feff81f

2 files changed

Lines changed: 95 additions & 1 deletion

File tree

pkg/utils/logger/logger.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,35 @@ import (
99
"sigs.k8s.io/controller-runtime/pkg/log/zap"
1010
)
1111

12+
// severityLevelEncoder encodes log levels to cloud-compatible severity names.
13+
// Uses "WARNING" instead of zap's "WARN", and maps DPANIC/PANIC/FATAL to CRITICAL.
14+
func severityLevelEncoder(level zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {
15+
switch level {
16+
case zapcore.WarnLevel:
17+
enc.AppendString("WARNING")
18+
case zapcore.DPanicLevel, zapcore.PanicLevel, zapcore.FatalLevel:
19+
enc.AppendString("CRITICAL")
20+
default:
21+
enc.AppendString(level.CapitalString())
22+
}
23+
}
24+
1225
func NewLogger() logr.Logger {
1326
opts := zap.Options{
14-
Development: true,
27+
// Use production mode for JSON output (enables structured logging in cloud environments).
28+
// Can be overridden with --zap-devel flag for local development.
29+
Development: false,
1530
StacktraceLevel: zapcore.DPanicLevel,
1631
TimeEncoder: zapcore.RFC3339TimeEncoder,
32+
// Configure encoder for cloud logging compatibility:
33+
// - Use "severity" field name (recognized by GKE, AKS, EKS log explorers)
34+
// - Use standard severity names (WARNING instead of WARN, CRITICAL for fatal errors)
35+
EncoderConfigOptions: []zap.EncoderConfigOption{
36+
func(ec *zapcore.EncoderConfig) {
37+
ec.LevelKey = "severity"
38+
ec.EncodeLevel = severityLevelEncoder
39+
},
40+
},
1741
}
1842
return zap.New(zap.UseFlagOptions(&opts))
1943
}

pkg/utils/logger/logger_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright Mondoo, Inc. 2026
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
package logger
5+
6+
import (
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
"go.uber.org/zap/zapcore"
11+
)
12+
13+
type testEncoder struct {
14+
result string
15+
}
16+
17+
func (t *testEncoder) AppendString(s string) {
18+
t.result = s
19+
}
20+
21+
// Implement remaining PrimitiveArrayEncoder methods as no-ops
22+
func (t *testEncoder) AppendBool(bool) {}
23+
func (t *testEncoder) AppendByteString([]byte) {}
24+
func (t *testEncoder) AppendComplex128(complex128) {}
25+
func (t *testEncoder) AppendComplex64(complex64) {}
26+
func (t *testEncoder) AppendFloat64(float64) {}
27+
func (t *testEncoder) AppendFloat32(float32) {}
28+
func (t *testEncoder) AppendInt(int) {}
29+
func (t *testEncoder) AppendInt64(int64) {}
30+
func (t *testEncoder) AppendInt32(int32) {}
31+
func (t *testEncoder) AppendInt16(int16) {}
32+
func (t *testEncoder) AppendInt8(int8) {}
33+
func (t *testEncoder) AppendUint(uint) {}
34+
func (t *testEncoder) AppendUint64(uint64) {}
35+
func (t *testEncoder) AppendUint32(uint32) {}
36+
func (t *testEncoder) AppendUint16(uint16) {}
37+
func (t *testEncoder) AppendUint8(uint8) {}
38+
func (t *testEncoder) AppendUintptr(uintptr) {}
39+
40+
func TestSeverityLevelEncoder(t *testing.T) {
41+
tests := []struct {
42+
level zapcore.Level
43+
expected string
44+
}{
45+
{zapcore.DebugLevel, "DEBUG"},
46+
{zapcore.InfoLevel, "INFO"},
47+
{zapcore.WarnLevel, "WARNING"}, // Cloud log explorers expect WARNING, not WARN
48+
{zapcore.ErrorLevel, "ERROR"},
49+
{zapcore.DPanicLevel, "CRITICAL"},
50+
{zapcore.PanicLevel, "CRITICAL"},
51+
{zapcore.FatalLevel, "CRITICAL"},
52+
}
53+
54+
for _, tt := range tests {
55+
t.Run(tt.level.String(), func(t *testing.T) {
56+
enc := &testEncoder{}
57+
severityLevelEncoder(tt.level, enc)
58+
assert.Equal(t, tt.expected, enc.result)
59+
})
60+
}
61+
}
62+
63+
func TestNewLogger(t *testing.T) {
64+
// Ensure NewLogger creates a valid logger without panicking
65+
logger := NewLogger()
66+
assert.NotNil(t, logger)
67+
68+
// Test that the logger can be used
69+
logger.Info("test message", "key", "value")
70+
}

0 commit comments

Comments
 (0)