events: add AnnotatedEventRecorder interface

Add a separate AnnotatedEventRecorder interface with an AnnotatedEventf
method that allows attaching annotations to events at creation time.
Implement it in recorderImpl, FakeRecorder, and EventRecorderAdapter.

Add a Verbose option to FakeRecorder that optionally includes action,
object kind/apiVersion, and annotations in event output. The default
format is unchanged.

Signed-off-by: Adrian Fernandez De La Torre <adri1197@gmail.com>

Kubernetes-commit: 31fe350b2b2065b49752adb4f68f1ea1c282058e
This commit is contained in:
Adrian Fernandez De La Torre
2026-03-30 15:43:47 +02:00
committed by Kubernetes Publisher
parent bdc99a38a9
commit e86b6659d9
6 changed files with 85 additions and 14 deletions

View File

@@ -41,9 +41,14 @@ type recorderImpl struct {
}
var _ EventRecorder = &recorderImpl{}
var _ AnnotatedEventRecorder = &recorderImpl{}
func (recorder *recorderImpl) Eventf(regarding runtime.Object, related runtime.Object, eventtype, reason, action, note string, args ...interface{}) {
recorder.eventf(klog.Background(), regarding, related, eventtype, reason, action, note, args...)
recorder.eventf(klog.Background(), regarding, related, nil, eventtype, reason, action, note, args...)
}
func (recorder *recorderImpl) AnnotatedEventf(regarding runtime.Object, related runtime.Object, annotations map[string]string, eventtype, reason, action, note string, args ...interface{}) {
recorder.eventf(klog.Background(), regarding, related, annotations, eventtype, reason, action, note, args...)
}
type recorderImplLogger struct {
@@ -54,14 +59,18 @@ type recorderImplLogger struct {
var _ EventRecorderLogger = &recorderImplLogger{}
func (recorder *recorderImplLogger) Eventf(regarding runtime.Object, related runtime.Object, eventtype, reason, action, note string, args ...interface{}) {
recorder.eventf(recorder.logger, regarding, related, eventtype, reason, action, note, args...)
recorder.eventf(recorder.logger, regarding, related, nil, eventtype, reason, action, note, args...)
}
func (recorder *recorderImplLogger) AnnotatedEventf(regarding runtime.Object, related runtime.Object, annotations map[string]string, eventtype, reason, action, note string, args ...interface{}) {
recorder.eventf(recorder.logger, regarding, related, annotations, eventtype, reason, action, note, args...)
}
func (recorder *recorderImplLogger) WithLogger(logger klog.Logger) EventRecorderLogger {
return &recorderImplLogger{recorderImpl: recorder.recorderImpl, logger: logger}
}
func (recorder *recorderImpl) eventf(logger klog.Logger, regarding runtime.Object, related runtime.Object, eventtype, reason, action, note string, args ...interface{}) {
func (recorder *recorderImpl) eventf(logger klog.Logger, regarding runtime.Object, related runtime.Object, annotations map[string]string, eventtype, reason, action, note string, args ...interface{}) {
timestamp := metav1.MicroTime{Time: time.Now()}
message := fmt.Sprintf(note, args...)
refRegarding, err := reference.GetReference(recorder.scheme, regarding)
@@ -81,14 +90,14 @@ func (recorder *recorderImpl) eventf(logger klog.Logger, regarding runtime.Objec
logger.Error(nil, "Unsupported event type", "eventType", eventtype)
return
}
event := recorder.makeEvent(refRegarding, refRelated, timestamp, eventtype, reason, message, recorder.reportingController, recorder.reportingInstance, action)
event := recorder.makeEvent(refRegarding, refRelated, timestamp, annotations, eventtype, reason, message, recorder.reportingController, recorder.reportingInstance, action)
go func() {
defer utilruntime.HandleCrash()
recorder.Action(watch.Added, event)
}()
}
func (recorder *recorderImpl) makeEvent(refRegarding *v1.ObjectReference, refRelated *v1.ObjectReference, timestamp metav1.MicroTime, eventtype, reason, message string, reportingController string, reportingInstance string, action string) *eventsv1.Event {
func (recorder *recorderImpl) makeEvent(refRegarding *v1.ObjectReference, refRelated *v1.ObjectReference, timestamp metav1.MicroTime, annotations map[string]string, eventtype, reason, message string, reportingController string, reportingInstance string, action string) *eventsv1.Event {
t := metav1.Time{Time: recorder.clock.Now()}
namespace := refRegarding.Namespace
if namespace == "" {
@@ -96,8 +105,9 @@ func (recorder *recorderImpl) makeEvent(refRegarding *v1.ObjectReference, refRel
}
return &eventsv1.Event{
ObjectMeta: metav1.ObjectMeta{
Name: util.GenerateEventName(refRegarding.Name, t.UnixNano()),
Namespace: namespace,
Name: util.GenerateEventName(refRegarding.Name, t.UnixNano()),
Namespace: namespace,
Annotations: annotations,
},
EventTime: timestamp,
Series: nil,

View File

@@ -299,7 +299,7 @@ func TestFinishSeries(t *testing.T) {
cache := map[eventKey]*eventsv1.Event{}
eventBroadcaster := newBroadcaster(&testEvents, 0, cache).(*eventBroadcasterImpl)
recorder := eventBroadcaster.NewRecorder(scheme.Scheme, "k8s.io/kube-foo").(*recorderImplLogger)
cachedEvent := recorder.makeEvent(regarding, related, metav1.MicroTime{Time: time.Now()}, v1.EventTypeNormal, "test", "some verbose message: 1", "eventTest", "eventTest-"+hostname, "started")
cachedEvent := recorder.makeEvent(regarding, related, metav1.MicroTime{Time: time.Now()}, nil, v1.EventTypeNormal, "test", "some verbose message: 1", "eventTest", "eventTest-"+hostname, "started")
nonFinishedEvent := cachedEvent.DeepCopy()
nonFinishedEvent.ReportingController = "nonFinished-controller"
cachedEvent.Series = &eventsv1.EventSeries{
@@ -386,7 +386,7 @@ func TestRefreshExistingEventSeries(t *testing.T) {
cache := map[eventKey]*eventsv1.Event{}
eventBroadcaster := newBroadcaster(&testEvents, 0, cache).(*eventBroadcasterImpl)
recorder := eventBroadcaster.NewRecorder(scheme.Scheme, "k8s.io/kube-foo").(*recorderImplLogger)
cachedEvent := recorder.makeEvent(regarding, related, metav1.MicroTime{Time: time.Now()}, v1.EventTypeNormal, "test", "some verbose message: 1", "eventTest", "eventTest-"+hostname, "started")
cachedEvent := recorder.makeEvent(regarding, related, metav1.MicroTime{Time: time.Now()}, nil, v1.EventTypeNormal, "test", "some verbose message: 1", "eventTest", "eventTest-"+hostname, "started")
cachedEvent.Series = &eventsv1.EventSeries{
Count: 10,
LastObservedTime: LastObservedTime,

View File

@@ -27,26 +27,71 @@ import (
// when created manually and not by NewFakeRecorder, however all events may be
// thrown away in this case.
type FakeRecorder struct {
Events chan string
Events chan string
Verbose bool
}
var _ EventRecorderLogger = &FakeRecorder{}
var _ AnnotatedEventRecorder = &FakeRecorder{}
// Eventf emits an event
func (f *FakeRecorder) Eventf(regarding runtime.Object, related runtime.Object, eventtype, reason, action, note string, args ...interface{}) {
if f.Events != nil {
f.Events <- fmt.Sprintf(eventtype+" "+reason+" "+note, args...)
}
f.writeEvent(regarding, related, nil, eventtype, reason, action, note, args...)
}
// AnnotatedEventf emits an event like Eventf, but with annotations attached
func (f *FakeRecorder) AnnotatedEventf(regarding runtime.Object, related runtime.Object, annotations map[string]string, eventtype, reason, action, note string, args ...interface{}) {
f.writeEvent(regarding, related, annotations, eventtype, reason, action, note, args...)
}
func (f *FakeRecorder) WithLogger(logger klog.Logger) EventRecorderLogger {
return f
}
// writeEvent constructs a string from the event parameters and sends
// it to the Events channel
func (f *FakeRecorder) writeEvent(regarding runtime.Object, related runtime.Object, annotations map[string]string, eventtype, reason, action, note string, args ...interface{}) {
if f.Events != nil {
msg := fmt.Sprintf(eventtype+" "+reason+" "+note, args...)
if f.Verbose {
msg = eventtype + " " + reason + " " + action + " " + fmt.Sprintf(note, args...) +
objectString(regarding) + objectString(related) + annotationsString(annotations)
}
f.Events <- msg
}
}
// annotationsString returns a formatted string of the annotations map,
// or empty string if none
func annotationsString(annotations map[string]string) string {
annotationString := ""
if len(annotations) > 0 {
annotationString = " " + fmt.Sprint(annotations)
}
return annotationString
}
// objectString returns a formatted string with the object's kind and
// apiVersion
func objectString(object runtime.Object) string {
objectString := ""
if object != nil {
gvk := object.GetObjectKind().GroupVersionKind()
if !gvk.Empty() {
objectString = fmt.Sprintf(" {kind=%s,apiVersion=%s}",
gvk.Kind,
gvk.GroupVersion(),
)
}
}
return objectString
}
// NewFakeRecorder creates new fake event recorder with event channel with
// buffer of given size.
func NewFakeRecorder(bufferSize int) *FakeRecorder {
return &FakeRecorder{
Events: make(chan string, bufferSize),
Events: make(chan string, bufferSize),
Verbose: false,
}
}

View File

@@ -28,6 +28,7 @@ import (
type EventRecorder = internalevents.EventRecorder
type EventRecorderLogger = internalevents.EventRecorderLogger
type AnnotatedEventRecorder = internalevents.AnnotatedEventRecorder
// EventBroadcaster knows how to receive events and send them to any EventSink, watcher, or log.
type EventBroadcaster interface {

View File

@@ -43,6 +43,15 @@ type EventRecorder interface {
Eventf(regarding runtime.Object, related runtime.Object, eventtype, reason, action, note string, args ...interface{})
}
// AnnotatedEventRecorder is an optional extension of EventRecorder that
// supports attaching annotations to events at creation time.
// Annotations are intended for low-frequency correlation metadata, not as a general purpose structured-data channel.
// Kubernetes events are best-effort, supplemental observability data and may be dropped, deduped or aggregated at any
// point in pipeline. Do not rely on annotations for event-driven control flow.
type AnnotatedEventRecorder interface {
AnnotatedEventf(regarding runtime.Object, related runtime.Object, annotations map[string]string, eventtype, reason, action, note string, args ...interface{})
}
// EventRecorderLogger extends EventRecorder such that a logger can
// be set for methods in EventRecorder. Normally, those methods
// uses the global default logger to record errors and debug messages.

View File

@@ -176,6 +176,12 @@ func (a *EventRecorderAdapter) Eventf(regarding, _ runtime.Object, eventtype, re
a.recorder.Eventf(regarding, eventtype, reason, note, args...)
}
// AnnotatedEventf is a wrapper around v1 AnnotatedEventf
func (a *EventRecorderAdapter) AnnotatedEventf(regarding, _ runtime.Object, annotations map[string]string, eventtype, reason, action, note string, args ...interface{}) {
//nolint:forbidigo // Legacy usage
a.recorder.AnnotatedEventf(regarding, annotations, eventtype, reason, note, args...)
}
func (a *EventRecorderAdapter) WithLogger(logger klog.Logger) internalevents.EventRecorderLogger {
return &EventRecorderAdapter{
recorder: a.recorder.WithLogger(logger),