David Bond 126f0ff0b4
k8s-operator: send operator logs to tailscale
This commit modifies the k8s operator to wrap its logger using the logtail
logger provided via the tsnet server. This causes any logs written by
the operator to make their way to Tailscale in the same fashion as
wireguard logs to be used by support.

Operator logs can be determined by the "k8s:" prefix within the log line.
This functionality can also be opted-out of entirely using the
"TS_NO_LOGS_NO_SUPPORT" environment variable.

This is implemented by reusing kzap's core with a different sink that
just writes to the logger.Logf function provided.

Fixes https://github.com/tailscale/corp/issues/32037

Signed-off-by: David Bond <davidsbond93@gmail.com>
2025-09-19 12:59:48 +01:00

42 lines
1.1 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build !plan9
package main
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
kzap "sigs.k8s.io/controller-runtime/pkg/log/zap"
"tailscale.com/types/logger"
)
type (
logfSink struct {
logf logger.Logf
}
)
// wrapZapCore returns a zapcore.Core implementation that splits the core chain using zapcore.NewTee. This causes
// logs to be simultaneously written to both the original core and the provided logger.Logf function.
func wrapZapCore(core zapcore.Core, logf logger.Logf) zapcore.Core {
// We use a tee logger here so that logs are written to stdout/stderr normally while at the same time being
// sent upstream.
return zapcore.NewTee(core, zapcore.NewCore(&kzap.KubeAwareEncoder{
Encoder: zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
Verbose: true,
}, &logfSink{logf: logf}, zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return true
})))
}
func (l *logfSink) Write(p []byte) (n int, err error) {
l.logf("k8s: %s", p)
return len(p), nil
}
func (l *logfSink) Sync() error {
return nil
}