4848 * can be overridden, see {@link
4949 * io.javaoperatorsdk.operator.api.config.ConfigurationService#clusterScopedEventNamespace()}.
5050 *
51- * <p>Events are named deterministically, after the object they are about plus a hash of everything
52- * that identifies the event, so that recording the same event again resolves to the event already
53- * recorded for it. Repeat occurrences are then counted on that event rather than recorded as copies
54- * of it, see {@link DefaultEventSink}.
51+ * <p>By default, events are named deterministically, after the object they are about plus a hash of
52+ * everything that identifies the event, so that recording the same event again resolves to the
53+ * event already recorded for it. Repeat occurrences are then counted on that event rather than
54+ * recorded as copies of it, see {@link DefaultEventSink}. How events aggregate, how they are named
55+ * and whether they carry an owner reference can be configured, see {@link #builder(EventSink)}.
5556 */
5657public class DefaultEventRecorder implements EventRecorder {
5758
@@ -74,16 +75,21 @@ public class DefaultEventRecorder implements EventRecorder {
7475 private static final int IDENTITY_HASH_LENGTH = 32 ;
7576
7677 private final EventSink sink ;
78+ private final EventNamingStrategy namingStrategy ;
7779 private final EventKeyStrategy keyStrategy ;
7880 private final boolean ownerReference ;
7981
8082 public DefaultEventRecorder (EventSink sink ) {
81- this (sink , EventKeyStrategy .none (), false );
83+ this (sink , EventNamingStrategy . none (), EventKeyStrategy .none (), false );
8284 }
8385
8486 private DefaultEventRecorder (
85- EventSink sink , EventKeyStrategy keyStrategy , boolean ownerReference ) {
87+ EventSink sink ,
88+ EventNamingStrategy namingStrategy ,
89+ EventKeyStrategy keyStrategy ,
90+ boolean ownerReference ) {
8691 this .sink = sink ;
92+ this .namingStrategy = namingStrategy ;
8793 this .keyStrategy = keyStrategy ;
8894 this .ownerReference = ownerReference ;
8995 }
@@ -96,16 +102,25 @@ public static Builder builder(EventSink sink) {
96102 public static final class Builder {
97103
98104 private final EventSink sink ;
105+ private EventNamingStrategy namingStrategy = EventNamingStrategy .none ();
99106 private EventKeyStrategy keyStrategy = EventKeyStrategy .none ();
100107 private boolean ownerReference = false ;
101108
102109 private Builder (EventSink sink ) {
103110 this .sink = Objects .requireNonNull (sink , "sink must not be null" );
104111 }
105112
113+ /** The strategy naming recorded events, see {@link EventNamingStrategy}. */
114+ public Builder namingStrategy (EventNamingStrategy namingStrategy ) {
115+ this .namingStrategy =
116+ Objects .requireNonNull (namingStrategy , "namingStrategy must not be null" );
117+ return this ;
118+ }
119+
106120 /**
107121 * The strategy deriving the default aggregation key of records that do not set one, see {@link
108- * EventKeyStrategy}.
122+ * EventKeyStrategy}. The key is ignored for events whose name is set by the record or resolved
123+ * by the naming strategy, see {@link EventNamingStrategy}.
109124 */
110125 public Builder keyStrategy (EventKeyStrategy keyStrategy ) {
111126 this .keyStrategy = Objects .requireNonNull (keyStrategy , "keyStrategy must not be null" );
@@ -126,7 +141,7 @@ public Builder ownerReference(boolean ownerReference) {
126141 }
127142
128143 public DefaultEventRecorder build () {
129- return new DefaultEventRecorder (sink , keyStrategy , ownerReference );
144+ return new DefaultEventRecorder (sink , namingStrategy , keyStrategy , ownerReference );
130145 }
131146 }
132147
@@ -162,14 +177,17 @@ private static String resolve() {
162177 public void record (EventRecord event , Context <?> context ) {
163178 Objects .requireNonNull (context , "the context of the reconciliation must not be null" );
164179 Objects .requireNonNull (event , "event must not be null" );
180+ Event assembled = null ;
165181 try {
166- sink .emit (toEvent (context , event ), context );
182+ assembled = toEvent (context , event );
183+ sink .emit (assembled , context );
167184 } catch (Exception e ) {
168185 // recording an event must never break the caller: a controller that fails to reconcile
169186 // because it could not write an event is strictly worse than one that records nothing
170187 log .warn (
171- "Could not record {} event with reason {} for resource {} in namespace {}" ,
188+ "Could not record {} event named {} with reason {} for resource {} in namespace {}" ,
172189 event .type (),
190+ assembled != null ? assembled .getMetadata ().getName () : "unknown" ,
173191 event .reason (),
174192 context .getPrimaryResource ().getMetadata ().getName (),
175193 context .getPrimaryResource ().getMetadata ().getNamespace (),
@@ -249,6 +267,30 @@ private String eventNamespace(HasMetadata regarding, Context<?> context) {
249267 CLUSTER_SCOPED_EVENT_NAMESPACE );
250268 }
251269
270+ private String eventName (HasMetadata regarding , EventRecord record , String reportingController ) {
271+ return record
272+ .name ()
273+ .filter (name -> !name .isBlank ())
274+ .or (() -> namingStrategy .nameFor (regarding , record ).filter (name -> !name .isBlank ()))
275+ .map (DefaultEventRecorder ::truncateToMaxNameLength )
276+ .orElseGet (() -> identityHashName (regarding , record , reportingController ));
277+ }
278+
279+ private static String truncateToMaxNameLength (String name ) {
280+ if (name .length () <= MAX_NAME_LENGTH ) {
281+ return name ;
282+ }
283+ var truncated = name .substring (0 , MAX_NAME_LENGTH );
284+ while (truncated .endsWith ("-" ) || truncated .endsWith ("." )) {
285+ truncated = truncated .substring (0 , truncated .length () - 1 );
286+ }
287+ log .warn (
288+ "Truncated the name of event {} to {} to stay within the Kubernetes name limit" ,
289+ name ,
290+ truncated );
291+ return truncated ;
292+ }
293+
252294 /**
253295 * Names events {@code <object name>.<hash>}, following the convention of the Go client, hashing
254296 * everything that makes two events the same event: the object, the type, the reason, the
@@ -259,8 +301,12 @@ private String eventNamespace(HasMetadata regarding, Context<?> context) {
259301 *
260302 * <p>The object is identified by its uid, with the kind as a fallback for objects that do not
261303 * have one yet, such as a dependent resource that has only been built so far.
304+ *
305+ * <p>This is the fallback when the record does not set a name and the naming strategy resolves to
306+ * nothing, see {@link EventNamingStrategy}.
262307 */
263- private String eventName (HasMetadata regarding , EventRecord record , String reportingController ) {
308+ private String identityHashName (
309+ HasMetadata regarding , EventRecord record , String reportingController ) {
264310 var metadata = regarding .getMetadata ();
265311 var identity =
266312 String .join (
0 commit comments