Handle a refused event¶
Unmarshal returns an
Event and an error, and on every error path the Event carries every attribute that could be
read before the refusal — extensions included.
It is only empty when there was nothing to read: a header map carrying no CloudEvents attributes at
all gives back the zero Event, because a message that is not a CloudEvent has no id to report.
That exists for one reason: core NATS has no dead letter. A handler that returns on error has destroyed the message, so if the log line says "a message failed to parse" the event is gone and unidentifiable.
The shape to copy¶
e, err := cloudevents.Unmarshal(msg.Header, msg.Data)
if err != nil {
refused.Add(1)
logger.Error("refused a CloudEvent",
"id", e.ID,
"source", e.Source,
"type", e.Type,
"err", err,
)
return
}
e.ID and e.Source together identify the occurrence, which is what makes the line actionable —
somebody can go and look at the producer.
Count the kind, not just the failure¶
Every refusal is a distinct sentinel, so a counter with a label costs nothing and tells you whether you have one broken producer or a version skew:
switch {
case errors.Is(err, cloudevents.ErrUnsupportedSpecVersion):
refused.Add(1, metric.WithAttributes(attribute.String("reason", "specversion")))
case errors.Is(err, cloudevents.ErrMissingAttribute):
refused.Add(1, metric.WithAttributes(attribute.String("reason", "missing")))
default:
refused.Add(1, metric.WithAttributes(attribute.String("reason", "other")))
}
The full list is in the errors reference.
Do not retry¶
Every refusal in this module is deterministic: the same bytes will be refused the same way for ever. Redelivering a malformed event only moves the failure later. Fix the producer, and if the event mattered, replay it from wherever the producer keeps its own record.