Skip to content

fix: nomear o motivo real do descarte de evento e tornar tipo sem rota visivel - #2

Merged
Bulletdev merged 1 commit into
mainfrom
fix/drop-reason-logging
Aug 23, 2026
Merged

fix: nomear o motivo real do descarte de evento e tornar tipo sem rota visivel#2
Bulletdev merged 1 commit into
mainfrom
fix/drop-reason-logging

Conversation

@Bulletdev

Copy link
Copy Markdown
Owner

O que esta PR faz?

Fecha as duas stories de observabilidade do PRD de contrato e entrega (US-03 e US-08), e um terceiro
defeito que apareceu durante a implementação.

O resolve_topics/1 devolve [] por dois motivos que não têm relação entre si, e o route_event/1
inferia o motivo a partir da lista vazia. Os dois casos logavam "Event missing required fields"
imprimindo, ao lado, um evento que tinha todos os campos obrigatórios. Quem depurasse isso em
produção era apontado para a causa errada por uma mensagem que o próprio evento desmentia.

Tipo da PR

Bugfix.

O que esta PR adiciona, remove ou atualiza?

  • Motivo do descarte separado. log_drop/1 distingue versão não suportada de campo obrigatório
    ausente, e no segundo caso nomeia qual campo faltou.
  • type não-string deixa de derrubar o subscriber. Um type que decodificava como número
    chegava no String.starts_with?/2 e levantava FunctionClauseError dentro do handle_info,
    matando o GenServer. O moduledoc diz que evento malformado não pode fazer isso. O
    resolve_topics/1 agora tem guard de binário, e campo não-binário é classificado como ausente.
  • Ramo não roteado sobe de debug para warning. Produção roda em :info para cima, então
    tipo que ninguém roteia era invisível - foi assim que nove tipos publicados passaram despercebidos.
  • Payload não é mais logado em nenhum desses caminhos. Ele pode carregar dado de usuário, e log
    é retido e agregado muito mais amplamente que o canal Redis de onde veio.
  • Cinco testes: os dois motivos, o campo não-binário, o payload não vazando, e o nível do warning.
  • @moduletag :capture_log, senão o warning novo polui a saída dos testes que não olham log.

Como testar?

mix test test/prostaff_events/redis_subscriber_test.exs

Para ver a diferença na prática, comparado com a main:

Evento Antes Depois
version: 99 Event missing required fields: %{...evento completo...} Rejected event: unsupported version=99 type="notification.created"
sem user_id mesma mensagem acima Dropped event: missing or invalid field(s): user_id
type: 123 FunctionClauseError, subscriber morre Dropped event: missing or invalid field(s): type
roster.player_hired [debug], invisível em produção [warning] Unrouted event type=roster.player_hired org=...

Resultado esperado

mix test em 60 testes, 0 falhas. Os seis portões verdes: format, compile --warnings-as-errors, credo --strict, sobelow --config --exit e dialyzer.

Nota de desenho

A docstring do resolve_topics/1 dizia "Pure function" e a função logava - não era verdade nem
antes desta PR. Ela continua emitindo o warning de tipo não roteado, de propósito, porque o type e
o org_id só estão em escopo ali. A docstring foi corrigida para dizer o que a função de fato
garante: nada de broadcast, e por isso a matriz de roteamento inteira é testável sem Redis.

Fora de escopo

Os tipos que hoje só chegam em org_events continuam sem destino assinável. Agora eles pelo menos
aparecem no log. Dar destino a eles é a próxima story, e ela tem uma decisão de desenho aberta:
canais por família ou expor org_events:*.

…ed types

Three defects in the same code path, all of them silent.

1. route_event/1 inferred the reason from an empty list. resolve_topics/1
   returns [] for two unrelated reasons, and both logged "Event missing
   required fields" while printing an event that had every required field in
   it. Whoever debugged that in production was pointed at the wrong cause by a
   message the event itself contradicted. The two cases are now told apart, and
   the missing-field case names which field.

2. A `type` that decoded as a number reached String.starts_with?/2 and raised
   FunctionClauseError inside handle_info, taking the subscriber down. The
   moduledoc says a malformed event must not do that. resolve_topics/1 now
   guards on binaries, so a non-binary field is classified as missing and
   logged, like any other malformed envelope.

3. The unrouted branch logged at :debug. Production runs at :info and up, so an
   event type nobody routes was invisible - which is how nine published types
   went unnoticed. It is a warning now: it means either a new type the
   publisher started sending or a typo, and both need a human.

The payload is no longer logged in any of these paths. It can carry user data,
and logs are retained and aggregated far more widely than the Redis channel
they came from.

  - route_event/1 delegates to log_drop/1; broadcast extracted
  - resolve_topics/1 no longer logs, and its docstring stops claiming a purity
    it never had (specific_topics/4 still warns, deliberately)
  - @supported_versions and @required_fields moved above first use
  - 5 tests covering both reasons, the non-binary field, the payload not
    leaking, and the warning level
  - @moduletag :capture_log so the new warning does not pollute the suite
@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 0 duplication

Metric Results
Duplication 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@Bulletdev Bulletdev self-assigned this Aug 23, 2026
@Bulletdev
Bulletdev merged commit 041e4ff into main Aug 23, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant