Repository navigation
operacao
Home > Plugins > Operação: log, erros, limites, saúde 🚧
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.
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.logFormat |
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(comMaxFileSize,RotateDaily,MaxFiles),Console, e o eventoOnLog(que roda na thread do pedido: seja rápido). -
LogHeaderspõe os cabeçalhos na linha JSON; os deRedactHeaderssaem como***(padrão:Authorization,Cookie,Set-Cookie,Proxy-Authorization).LogBodiespõe o corpo, atéMaxBodyLogbytes. -
bytesé-numa resposta em fluxo (o tamanho não é conhecido).
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-
ExceptionMapacha a classe ou o ancestral mais próximo. -
DeveloperModeacrescenta a classe, a mensagem e a pilha (OnGetStackTrace) só para pedidos deDeveloperIPs(padrão127.0.0.1e::1); um navegador (Accept: text/html) recebe uma página.HideErrorDetailscontinua valendo para os demais. -
OnFormatErrordeixa você montar o próprio formato.
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 eventoOnGetKey(chave vazia = fora do limite). - Passou do limite: 429 com
Retry-After, e toda resposta levaRateLimit-PolicyeRateLimit(quantos restam, em quantos segundos zera). - Vários servidores atrás de um balanceador: dê a todos o mesmo
Store(umTRALFileStorenuma pasta compartilhada) e o limite passa a ser a soma.
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 osChecks; 200 se todos passam, 503 se um falha. Para o balanceador saber se manda pedidos. Corpoapplication/health+json. -
TRALMetricsPluginresponde em/metrics(Path) no formato do Prometheus:ral_http_requests_total(por método, rota e status),ral_http_request_duration_seconds(histograma) eral_http_requests_in_flight. A rota é o padrão (/users/:id), nunca a URL, para não criar uma série por id.OnCollectacrescenta 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
TRALWhiteListPluginou num proxy.
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.
-
TRALHttpsRedirectPlugin: um pedido em HTTP recebe 301 (GET, HEAD) ou 308 (os outros métodos, que mantêm método e corpo) parahttps://, naHttpsPort. Atrás de um proxy que termina o TLS, ligueTrustForwardedProto(só se o proxy sempre mandaX-Forwarded-Proto).HstsMaxAgepõeStrict-Transport-Securitynas respostas em HTTPS. -
TRALMaintenancePlugin: comActive := True, todo pedido recebe 503 comRetry-After(RetryAfter, em segundos) eMessage, menos os deAllowIPse os caminhos deAllowPaths(a rota de saúde, a administração). -
TRALMethodOverridePlugin: numPOST, o cabeçalhoX-HTTP-Method-Override(ou o parâmetro_method,QueryParam) troca o método, só para os deMethods(padrão PUT, PATCH, DELETE). Para clientes e firewalls que só deixam passar GET e POST.
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
CacheRoutese responde delas (X-Cache: HIT,Age) até o TTL vencer. A chave é caminho + query + os cabeçalhos deVaryHeaders(padrãoAccept). - Um pedido com
Authorizationou cookie nunca sai do cache nem entra nele (CacheAuthorizedlibera, só para rotas que respondem igual a todos). -
DynamicETag(padrão ligado) dá a toda resposta GET de atéMaxBodySizebytes umETagforte, e responde 304 a umIf-None-Matchque o cita, mesmo fora do cache: o corpo não viaja de novo.
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).
Cliente.Retry.MaxAttempts := 3; // 1 = não repete (padrão)
Cliente.Retry.Backoff := rbExponential; // 200 ms, 400 ms, 800 ms...
Cliente.CircuitBreaker.Enabled := True;-
Retryrepete quando a resposta é um status deRetryOn(padrão408,429,502,503,504) ou, comRetryTransport, quando a conexão falha. EsperaDelayms (rbConstant,rbLinearourbExponential, comJitter, atéMaxDelay) ou oRetry-Afterdo servidor. Só repete os métodos idempotentes (GET, HEAD, PUT, DELETE, OPTIONS, TRACE, QUERY): POST e PATCH só comRetryNonIdempotent. Nunca repete depois que um pedaço da resposta foi entregue (OnReceiveData). -
CircuitBreaker: depois deFailureThresholdfalhas seguidas (5xx ou rede) no mesmoservidor:porta, o circuito abre e os pedidos falham na hora comTransportError = rteCircuitOpen, sem rede, porOpenTimems. Depois deixa passarHalfOpenProbespedidos de teste: deram certo, fecha.
| 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
|
Começar
Servidor
- Rotas e parâmetros
- Rotas avançadas: tipos, grupos, versões 🚧
- Ler o pedido e responder
- Resposta em fluxo 🚧
- Plugins 🚧
- Autenticação
- Banco de dados (DBWare)
- Servir site e arquivos
- Swagger e Postman
- Servidor SOAP 🚧
- HTTPS
- Criptografia ponto a ponto
- Segurança para produção
- Colocar no ar
- Desempenho
MQTT 🚧
- O que é e quando usar 🚧
- Cliente MQTT 🚧
- Broker MQTT 🚧
- MQTT-SN 🚧
- Ponte REST 🚧
- Segurança 🚧
- Rede ruim 🚧
Cliente
Motores
Avançado
Ajuda
- Problemas comuns
- Atualizar o Indy
- Várias versões do Delphi
- Vídeos e guias
- Módulos de terceiros
- Licenças
- Contribuir
- Roadmap e changelog
🚧 = em desenvolvimento, pode mudar