Skip to content
mobius1qwe edited this page Nov 1, 2026 · 1 revision

Home > Plugins > Operação: log, erros, limites, saúde 🚧

Operação: log, erros, limites, saúde e métricas

Important

Em desenvolvimento (2.0). Ainda não está numa versão lançada e pode mudar.

Vale para a 2.0 · Delphi XE em diante e Lazarus/FPC 3.2 em diante · Nível: intermediário

Em uma frase: o que um servidor em produção pede no primeiro dia: uma linha de log por pedido, erros num formato padrão, limite de pedidos por cliente, rotas de saúde e de métricas, rastreio entre serviços, e no cliente repetir com calma quando o outro lado falha.

Você vai precisar: saber ligar um plugin. Tudo aqui é opcional: o servidor sem esses plugins funciona como antes.

pacote componentes (paleta RAL - Plugins)
RALPluginObservabilityPackage TRALLogPlugin, TRALTracePlugin, TRALMetricsPlugin, TRALHealthPlugin
RALPluginTrafficPackage TRALRateLimitPlugin, TRALHttpsRedirectPlugin, TRALMaintenancePlugin, TRALMethodOverridePlugin
RALPluginCachePackage TRALResponseCachePlugin
PascalRAL (o pacote-base) TRALMemoryStore, TRALFileStore; TRALServer.ErrorFormat; TRALClient.Retry e CircuitBreaker

Como todo plugin: solte no formulário e aponte Server para o servidor, ou pelo código Plugin.Server := Servidor.

Log de acesso (TRALLogPlugin)

Uma linha por pedido, escrita quando a resposta está pronta (já sabe status, bytes e tempo):

Log := TRALLogPlugin.Create(Self);
Log.Server := Servidor;
Log.Format := lfJSON;
Log.FileName := 'logs\api.log';
Log.MaxFileSize := 10 * 1024 * 1024; // gira com 10 MB
Log.MaxFiles := 7;                   // guarda api.1.log ... api.7.log
Format a linha
lfCommon (padrão) 10.0.0.1 - ana [01/Nov/2026:13:05:01 +0000] "GET /users/7 HTTP/1.1" 200 42
lfCombined a mesma, mais "referer" "user-agent"
lfJSON time, remote, method, path, route (/users/:id), status, bytes, duration_ms, user, user_agent, trace_id
  • Destino: FileName (com MaxFileSize, RotateDaily, MaxFiles), Console, e o evento OnLog (que roda na thread do pedido: seja rápido).
  • LogHeaders põe os cabeçalhos na linha JSON; os de RedactHeaders saem como *** (padrão: Authorization, Cookie, Set-Cookie, Proxy-Authorization). LogBodies põe o corpo, até MaxBodyLog bytes.
  • bytes é - numa resposta em fluxo (o tamanho não é conhecido).

Erros num formato padrão (ErrorFormat)

Os erros que o próprio RAL monta (404 sem rota, 405, 413, 415, 429, 503 e o 500 de exceção) são texto. Com ErrorFormat := efProblemJSON viram o JSON da RFC 9457, que front-ends e gateways tratam de um jeito só:

{"type": "about:blank", "title": "Not Found", "status": 404, "instance": "/nada", "traceId": "4bf92f35..."}
Servidor.ErrorFormat := efProblemJSON;
// uma exceção sua vira o status certo, não 500
Servidor.ExceptionMap.AddMapping('EValidacao', 422, 'https://minhaapi/erros/validacao');

No handler, responda no mesmo formato:

AResponse.AnswerProblem(HTTP_NotFound, 'cliente 7 não existe');
AResponse.AnswerError(HTTP_Conflict);   // segue o ErrorFormat do servidor
  • ExceptionMap acha a classe ou o ancestral mais próximo.
  • DeveloperMode acrescenta a classe, a mensagem e a pilha (OnGetStackTrace) só para pedidos de DeveloperIPs (padrão 127.0.0.1 e ::1); um navegador (Accept: text/html) recebe uma página. HideErrorDetails continua valendo para os demais.
  • OnFormatError deixa você montar o próprio formato.

Limite de pedidos (TRALRateLimitPlugin)

Limite := TRALRateLimitPlugin.Create(Self);
Limite.Server := Servidor;
Limite.Limit := 100;        // pedidos
Limite.Window := 60000;     // por minuto
Limite.Rules.AddRule('/login', 5, 60000); // o login, mais apertado
  • Algoritmo: rlFixedWindow (padrão), rlSlidingWindow (sem o pico na virada da janela), rlTokenBucket (aceita rajada e repõe aos poucos).
  • Quem é o cliente (Key): rkIP (padrão), rkUser (o usuário do Basic), rkHeader (KeyHeader, por exemplo uma chave de API), rkRoute (um limite para todos), ou o evento OnGetKey (chave vazia = fora do limite).
  • Passou do limite: 429 com Retry-After, e toda resposta leva RateLimit-Policy e RateLimit (quantos restam, em quantos segundos zera).
  • Vários servidores atrás de um balanceador: dê a todos o mesmo Store (um TRALFileStore numa pasta compartilhada) e o limite passa a ser a soma.

Saúde (TRALHealthPlugin) e métricas (TRALMetricsPlugin)

Saude := TRALHealthPlugin.Create(Self);
Saude.Server := Servidor;
Saude.Checks.AddCheck('banco', VerificarBanco);

procedure TForm1.VerificarBanco(ACheck: TRALHealthCheck; var AHealthy: boolean;
  var AOutput: StringRAL);
begin
  AHealthy := Conexao.Ping;
  if not AHealthy then
    AOutput := 'banco parado';
end;
  • /health/live (LivePath): 200 enquanto o processo responde. Para o orquestrador (Kubernetes, Docker) saber se reinicia.
  • /health/ready (ReadyPath): roda os Checks; 200 se todos passam, 503 se um falha. Para o balanceador saber se manda pedidos. Corpo application/health+json.
  • TRALMetricsPlugin responde em /metrics (Path) no formato do Prometheus: ral_http_requests_total (por método, rota e status), ral_http_request_duration_seconds (histograma) e ral_http_requests_in_flight. A rota é o padrão (/users/:id), nunca a URL, para não criar uma série por id. OnCollect acrescenta as suas.
  • As duas rotas são públicas: não pedem autenticação. Se elas não devem ficar expostas, limite por IP com o TRALWhiteListPlugin ou num proxy.

Rastreio entre serviços (TRALTracePlugin)

Lê o traceparent (W3C Trace Context) do pedido, ou começa um rastreio, põe o id em ARequest.TraceId e responde traceresponse. O log JSON e o problem details levam o mesmo id. Um TRALClient chamado de dentro do handler, na mesma thread, manda o traceparent adiante: o próximo serviço RAL continua o mesmo rastreio.

Com ExportURL (por exemplo http://coletor:4318/v1/traces), os spans vão para um coletor OpenTelemetry (OTLP/HTTP, JSON) em lotes, a cada ExportInterval ms, numa thread; ServiceName dá o nome do serviço. Sem ExportURL, use o evento OnSpan.

HTTPS forçado, manutenção, method override

  • TRALHttpsRedirectPlugin: um pedido em HTTP recebe 301 (GET, HEAD) ou 308 (os outros métodos, que mantêm método e corpo) para https://, na HttpsPort. Atrás de um proxy que termina o TLS, ligue TrustForwardedProto (só se o proxy sempre manda X-Forwarded-Proto). HstsMaxAge põe Strict-Transport-Security nas respostas em HTTPS.
  • TRALMaintenancePlugin: com Active := True, todo pedido recebe 503 com Retry-After (RetryAfter, em segundos) e Message, menos os de AllowIPs e os caminhos de AllowPaths (a rota de saúde, a administração).
  • TRALMethodOverridePlugin: num POST, o cabeçalho X-HTTP-Method-Override (ou o parâmetro _method, QueryParam) troca o método, só para os de Methods (padrão PUT, PATCH, DELETE). Para clientes e firewalls que só deixam passar GET e POST.

Cache de respostas e ETag (TRALResponseCachePlugin)

Cache := TRALResponseCachePlugin.Create(Self);
Cache.Server := Servidor;
Cache.CacheRoutes.AddRoute('/produtos', 30); // 30 segundos
  • Guarda as respostas 200 de GET e HEAD das rotas em CacheRoutes e responde delas (X-Cache: HIT, Age) até o TTL vencer. A chave é caminho + query + os cabeçalhos de VaryHeaders (padrão Accept).
  • Um pedido com Authorization ou cookie nunca sai do cache nem entra nele (CacheAuthorized libera, só para rotas que respondem igual a todos).
  • DynamicETag (padrão ligado) dá a toda resposta GET de até MaxBodySize bytes um ETag forte, e responde 304 a um If-None-Match que o cita, mesmo fora do cache: o corpo não viaja de novo.

Os stores (TRALMemoryStore, TRALFileStore)

Onde o limite de pedidos e o cache guardam as contas. Sem Store, cada plugin usa a própria memória. TRALMemoryStore é rápido e some quando o processo para; TRALFileStore grava um arquivo por chave em Folder (padrão: ralstore na pasta temporária) e serve a vários processos na mesma pasta (cada operação trava o arquivo, até LockTimeout ms).

No cliente: repetir e desistir (Retry, CircuitBreaker)

Cliente.Retry.MaxAttempts := 3;         // 1 = não repete (padrão)
Cliente.Retry.Backoff := rbExponential; // 200 ms, 400 ms, 800 ms...
Cliente.CircuitBreaker.Enabled := True;
  • Retry repete quando a resposta é um status de RetryOn (padrão 408,429,502,503,504) ou, com RetryTransport, quando a conexão falha. Espera Delay ms (rbConstant, rbLinear ou rbExponential, com Jitter, até MaxDelay) ou o Retry-After do servidor. Só repete os métodos idempotentes (GET, HEAD, PUT, DELETE, OPTIONS, TRACE, QUERY): POST e PATCH só com RetryNonIdempotent. Nunca repete depois que um pedaço da resposta foi entregue (OnReceiveData).
  • CircuitBreaker: depois de FailureThreshold falhas seguidas (5xx ou rede) no mesmo servidor:porta, o circuito abre e os pedidos falham na hora com TransportError = rteCircuitOpen, sem rede, por OpenTime ms. Depois deixa passar HalfOpenProbes pedidos de teste: deram certo, fecha.

Erros comuns

Sintoma Causa Solução
a linha de log não aparece o plugin não está ligado ao servidor Log.Server := Servidor
user sempre - no log o campo é o usuário do login Basic; com JWT ou sem login sai - para outro usuário, escreva a linha no OnLog com o que o seu handler sabe
429 para todos os clientes juntos Key = rkRoute, ou um proxy na frente: todos têm o mesmo IP rkHeader com a chave da API, ou OnGetKey lendo o IP real
o limite não soma entre dois servidores cada um com a sua memória o mesmo TRALFileStore (Store) nos dois
/health/ready em 503 um Check deu AHealthy := False leia o output no JSON da resposta
a resposta não vem do cache o pedido tem Authorization ou cookie, ou o status não é 200 CacheAuthorized só para rotas iguais para todos
redirecionamento para HTTPS em laço o proxy termina o TLS e o RAL vê HTTP TrustForwardedProto := True
rteCircuitOpen sem o servidor ter caído agora o circuito abriu antes e ainda conta OpenTime espere, ou aumente FailureThreshold

Veja também

Clone this wiki locally