Desenvolvimento

Debugging avançado em ADVPL: técnicas que funcionam

Além do console.log — ferramentas reais para encontrar e resolver bugs em código Protheus.

Debugging em ADVPL tem fama de ser limitado. Na prática, o TOTVS Protheus oferece ferramentas mais poderosas do que a maioria dos desenvolvedores conhece. O problema não é a falta de ferramentas — é não saber onde procurar.

Log estruturado antes de console

O padrão oficial TOTVS (regra CA1004) pede log via FWLogMsg() — não via console: o console não é audível em produção distribuída. ConOut() fica reservado para debug temporário, sempre com prefixo [DEBUG] e remoção obrigatória antes do merge.

ADVPL — Log de produção × debug temporário
// Log de produção — FWLogMsg (CA1004).
// Adapte a assinatura à sua release — ver TDN.
FWLogMsg("INFO", "", "MINHAROTINA", "", "", "", "Pedido " + cNum + " processado")

// Debug temporário — ConOut com prefixo [DEBUG], remover antes do merge
Static Function DebugVar(cVar, cNome)
  Local cTipo := ValType(cVar)
  Local cConteudo := ""

  Do Case
    Case cTipo == "C"
      cConteudo := cVar
    Case cTipo == "N"
      cConteudo := AllTrim(Str(cVar))
    Case cTipo == "L"
      // If/Else explícito — regra CA4000 proíbe IF()/IIF() inline
      If cVar
        cConteudo := ".T."
      Else
        cConteudo := ".F."
      EndIf
    Case cTipo == "D"
      cConteudo := DtoC(cVar)
    Case cTipo == "A"
      cConteudo := "Array[" + AllTrim(Str(Len(cVar))) + "]"
    Case cTipo == "U"
      cConteudo := "NIL"
  EndCase

  ConOut("[DEBUG] " + cNome + " (" + cTipo + ") = " + cConteudo)
Return
Dica

Para debugging temporário, use o prefixo [DEBUG] no log — facilita encontrar e limpar depois. No Windows, use a busca do TDS/VSCode (busca em pasta) para localizar todos os pontos de debug no final do desenvolvimento — grep não é nativo do ambiente Windows do dev Protheus.

Breakpoints condicionais no TOTVS Studio

O TOTVS Studio (ou ADVPL IDE) suporta breakpoints condicionais — você só pausa quando uma condição é atendida:

Breakpoint condicional
// Cenário: bug só acontece quando pedido tem mais de 10 itens
// Em vez de pausar em TODOS os pedidos, pare só no problema:

Static Function GravaPedido()
  // ← BREAKPOINT AQUI com condição: Len(aItens) > 10
  Local nTotal := Len(aItens)

  If nTotal > 10
    // Ponto suspeito — investigar lógica de rateio
    ProcessaRateio(aItens)
  EndIf
Return

Rastreamento de performance com elapsed time

Quando uma rotina está lenta, o problema raramente é o código em si — é a consulta SQL ou acesso a disco. Meça antes de otimizar:

ADVPL — Medição de performance
Static Function BenchmarkQuery()
  Local nStart := Seconds()
  Local cQuery := ""
  Local nRegs := 0

  cQuery := "SELECT COUNT(*) AS TOTAL FROM " + RetSqlName("SC5")
  cQuery += " WHERE C5_FILIAL = '" + xFilial("SC5") + "'"
  cQuery += " AND D_E_L_E_T_ = ' '"

  dbUseArea(.T., "TOPCONN", TcGenQry(,, cQuery), "TMPBENCH", .T., .T.)
  nRegs := TMPBENCH->TOTAL
  dbCloseArea()

  ConOut("[BENCH] Query: " + AllTrim(Str(nRegs)) + " registros em " + ;
         AllTrim(Str(Round(Seconds() - nStart, 3))) + "s")
Return

Tratamento de erros com ErrorBlock e Begin Sequence

Em .prw (ADVPL), o mecanismo é BEGIN SEQUENCE ... RECOVER USING — Try/Catch é TL++ e não compila em ADVPL. O ErrorBlock intercepta o erro e o Break do handler propaga para o RECOVER do escopo chamador:

ADVPL — ErrorBlock avançado
Static Function ExecutaComErro()
  Local bOldError := ErrorBlock({|oErr| TrataErro(oErr)})
  Local oErr := Nil

  Begin Sequence
    // Código que pode falhar
    ExecutaRotinaCritica()

  Recover Using oErr
    // Chegou aqui via Break do handler — logar e decidir
    ConOut("[ERROR] Rotina recuperada")

  End Sequence

  ErrorBlock(bOldError) // Restaurar handler anterior
Return

Static Function TrataErro(oErr)
  // Logue o essencial e verificável: Description, Operation, GenCode
  // (propriedades exatas do objeto de erro variam por release — valide)
  ConOut("[ERROR] " + oErr:Description)
  ConOut("[ERROR] Operacao: " + oErr:Operation)

  // Break dentro do handler do ErrorBlock propaga o erro
  // para o RECOVER USING do Begin Sequence ativo.
  // Retry/Break só valem no escopo do próprio Begin Sequence —
  // nunca em rotina separada fora do handler.
  Break oErr
Return

Memória: as ferramentas certas

Não existe "varredura de variáveis" confiável no runtime ADVPL — truques com laços de GetMemVar() não auditam memória real. Para investigar consumo excessivo:

  • Monitor do AppServer: acompanhe uso por thread/serviço no monitor do AppServer (e no monitor de jobs).
  • Work Areas orfãs: alias temporário aberto e nunca fechado vaza memória e travas. Feche sempre — TcCanOpen/dbCloseArea.
  • FWTemporaryTable: em vez de criar tabelas ISAM físicas de trabalho (regra CA1000), use tabela temporária do framework — o banco cuida da limpeza.
  • Batch: prefira processar em blocos a carregar 100k registros em array.

Debugging em Jobs e AppServer

Rotinas que rodam como Job (sem interface gráfica) são as mais difíceis de debugar. Sem botão, sem tela, sem breakpoint visual. A solução: log estruturado — com append no arquivo, nunca MemoWrite (que sobrescreve o arquivo a cada chamada e deixa só a última linha).

ADVPL — Log estruturado para Jobs
// Log de job com APPEND (#Include "fileio.ch" para FO_APPEND/FS_END)
Static Function JobLog(cMsg, cLevel)
  Local nHnd := -1
  Local cLog := DToC(Date()) + " " +;
    Time() + " [" + cLevel + "] " + cMsg

  // Log estruturado oficial (CA1004) — adapte à sua release
  FWLogMsg(cLevel, "", "MEUJOB", "", "", "", cMsg)

  // Arquivo de auditoria — append, não sobrescreve
  // ATENÇÃO: caminho é do SERVIDOR AppServer, não do cliente
  nHnd := FOpen("\totvs\logs\job_" +;
    DTOS(Date()) + ".log", FO_APPEND + FO_READWRITE)
  If nHnd > 0
    FSeek(nHnd, 0, FS_END)
    FWrite(nHnd, cLog + CRLF)
    FClose(nHnd)
  EndIf
Return

// Uso em um Job (.prw): BEGIN SEQUENCE — Try/Catch é TL++ e não compila em .prw
User Function MeuJob()
  Local oErr := Nil

  JobLog("Início do processamento", "INFO")

  Begin Sequence
    // ... processamento ...
    JobLog("Processados 1500 registros", "INFO")

  Recover Using oErr
    JobLog("ERRO: " + oErr:Description, "ERROR")

  End Sequence

  JobLog("Fim do processamento", "INFO")
Return

Dica: use prefixos no log ([INFO], [ERROR], [WARN]) para facilitar busca. E cuidado com Seconds() em medições: ela reinicia à meia-noite — se a medição puder atravessar 00:00, use base de tempo com data/hora (ex.: FWTimeStamp).

Checklist de debugging ADVPL

1
Reproduzir o bug — antes de tudo, garanta que você consegue acionar o erro de forma consistente.
2
Isolar a variável — use DebugVar() para entender o tipo e conteúdo antes do ponto de falha.
3
Medir performance — se é lento, meça antes de otimizar. O problema pode ser SQL, não ADVPL.
4
ErrorBlock — se o erro é intermitente, englobe com ErrorBlock e registre o stack trace completo.
5
Produção vs Homologação — muitos bugs só aparecem com dados reais. Teste com volume de produção.

Precisa de ajuda com debugging Protheus?

Falar com a ELP