Ingestão
Implementado em backend/controller/evento/criar.rs e backend/model/evento.rs. É a única rota sem token: fica em router_publico().
Objetivo
Receber muito evento com resposta rápida. A rota só valida e enfileira; a gravação e as agregações acontecem depois, numa tarefa em segundo plano.
Entradas e saídas
| Ponto | Descrição |
|---|---|
| Entrada | POST /evento/ingestao com a chave, os dados da sessão e a lista de eventos, em JSON ou gzip |
| Saída | 202 com a contagem de aceitos e recusados |
| Próximo passo | A fila grava eventos, sessão, erros e uso |
{
"chave": "pk_live_…",
"sessao": {
"sessao_externa": "…", "anonimo_id": "…", "usuario_id": null,
"navegador": "…", "sistema_operacional": "…", "dispositivo": "…",
"resolucao": "…", "pagina_inicial": "…"
},
"eventos": [
{ "evento_id": "…", "tipo": "ERRO_JS", "nome": "…", "mensagem": "…", "ocorrido_em": "…" }
]
} Os demais campos do evento são opcionais: pilha, arquivo, linha, coluna, url, metodo, status, duracao_ms, nivel, seletor, texto, metrica, valor, usuario_id e payload.
Etapas
1. Leitura do corpo
Corpo com content-encoding: gzip é descompactado. Corpo ilegível responde 400.
2. Chave
A chave é procurada entre os aplicativos não excluídos. Chave desconhecida responde 401.
3. Origem
A origem vem do Origin ou, na falta, do Referer, sem protocolo, caminho e ponto final. Ela passa se for igual a um domínio do aplicativo, se tiver o mesmo host ignorando a porta ou se casar com um curinga *.raiz. A origem do próprio painel (URL_FRONTEND) sempre passa. Fora isso, 403.
A chave roda no site do cliente e é pública. Quem autoriza a ingestão é essa conferência da origem contra os domínios do aplicativo, não o CORS, que aceita qualquer origem para POST e OPTIONS.
4. Limites
| Regra | Resultado |
|---|---|
| Lista de eventos vazia | 202 com zero aceitos, sem criar sessão |
| Mais de 500 eventos no lote ou tipo inválido | O excedente e os inválidos viram recusados |
| Mais de 6.000 eventos por minuto na chave | Lote inteiro recusado e registrado no uso, 429 |
Franquia estourada com PARAR_INGESTAO | Lote inteiro recusado e registrado no uso, 402 |
O limite por minuto fica na memória do processo do backend.
5. Sessão
A sessão é procurada pelo par aplicativo_id + sessao_externa e criada se não existir; um índice único resolve lotes simultâneos. Os dados de uma sessão já existente não são atualizados. O país vem do cabeçalho cf-ipcountry ou x-vercel-ip-country, senão fica DESCONHECIDO. O IP não é guardado.
6. Fila
O lote entra num canal com um único consumidor e a rota responde 202. O número de aceitos conta todos os válidos, inclusive PULSO.
Processamento em segundo plano
PULSOnão é gravado: só marca atividade na sessão e não conta no uso.- O id do evento é um UUIDv5 de
aplicativo_id:evento_ide a gravação usaON CONFLICT DO NOTHING. Reenvio do mesmo lote não duplica. - Campos longos são cortados:
mensagem2.000,pilha8.000,urlearquivo2.000,seletor500,texto200,nome120 caracteres. IDENTIFICACAOgrava ousuario_idna sessão.ERRO_JS,PROMISE_REJEITADAeCONSOLEcomnivel = errorentram em Erros .DESEMPENHOcommetrica = CARREGAMENTOgrava otempo_carregamento_msda sessão.- No fim do lote a sessão soma
quantidade_eventosequantidade_erros,ultima_atividade_emsó avança, o uso do dia é somado, os avisos de franquia são conferidos eprimeiro_evento_emé preenchido se estiver vazio.
Retenção
Uma tarefa roda a cada hora (a primeira, uma hora depois do boot) e apaga de verdade o que passou de 30 dias:
| Tabela | Critério |
|---|---|
sessao | ultima_atividade_em antiga. Os eventos dela caem junto em cascata |
evento | criado_em antigo |
erro | ultima_ocorrencia_em antiga |
A tabela uso não passa por retenção. Fora daqui, toda exclusão é lógica (deletado = true).