Skip to content

Alert on unvalidated messages

A guard that cannot resolve a schema processes the message anyway and increments a counter. That counter is the load-bearing half of the design, and a deployment that does not export it has taken the fail-open without the "loudly".

Read it directly

guard.Unvalidated()  // uint64, monotonic

Or wire a callback

unvalidated, err := meter.Int64Counter("schema.unvalidated",
    metric.WithDescription("messages processed without validating them"))
if err != nil {
    return err
}

guard := schema.NewGuard(resolver, validators,
    schema.OnFailOpen(func(ctx context.Context, dataschema string, cause error) {
        unvalidated.Add(ctx, 1, metric.WithAttributes(
            attribute.String("dataschema", dataschema),
        ))

        logger.WarnContext(ctx, "processed a message unvalidated",
            "dataschema", dataschema, "cause", cause)
    }),
)

It is a callback rather than a metric instrument so that this module does not put an observability stack into the graph of every service that reads a message. The counter is the requirement; the SDK is your choice.

What to alert on

schema.unvalidated should be zero in normal operation. Alert on it being anything else.

That is only true because of what is deliberately not counted:

Case Counted?
An event with no dataschema at all No. It claims no schema; nothing failed
Payload resolved and did not conform No. That is a validation failure, and it fails closed
dataschema is not a URN we recognise Yes
A URN this service does not hold Yes
The registry is unreachable Yes
No validator registered for the schema's language Yes
The schema resolved but will not compile, or names no root Yes

A counter that ticked during healthy running would be a counter nobody could alert on, which is why the first two rows matter as much as the last four.

What it is usually telling you

A deleted embedded schema. The commonest cause by a distance: a service stopped emitting v1, so somebody removed orders.created.v1.json, and the stream still holds a year of v1 messages.

A version skew. A producer deployed v4 before consumers embedded it.

A missing validator. The schema resolved fine; nothing was registered that could read it.

A mis-published schema. A protobuf schema published without its root message, or a JSON Schema that will never compile. Worth calling out because it is the case that used to fail closed: the guard now lets the message through and counts it, so the counter is how you find out rather than a support ticket saying a topic has stopped working.

An actual outage, if the guard is backed by the network resolver.

The first three are fixed by embedding what you consume. Only the last is the registry's fault, and it is the one the fail-open exists for.