Como ler e gravar registros de aplicativos

Visão geral

Quando uma solicitação é enviada para seu aplicativo, um registro de solicitação é gravado automaticamente pelo App Engine. Durante o tratamento da solicitação, seu app também pode gravar registros de aplicativos. Nesta página, você aprenderá a gravar registros por meio do seu aplicativo, ler os registros e as solicitações de maneira programática usando a Logs API e como visualizar a geração de registros no console do Google Cloud Platform. Você também compreenderá os dados do registro que o App Engine grava durante a solicitação.

Comparação entre registros de solicitação e de aplicativos

Há duas categorias de dados de registros: de solicitação e de aplicativos. Os registros de solicitação são gravados automaticamente pelo App Engine para cada solicitação processada pelo seu aplicativo e contêm informações como o código do projeto, a versão HTTP e assim por diante. Para uma lista completa das propriedades disponíveis para os registros de solicitação, consulte RequestLogs. Consulte também a tabela de registros de solicitação para descrições dos campos desses registros.

Cada registro de solicitação contém uma lista de registros de aplicativos (AppLogLine) associada a essa solicitação, retornada no método RequestLogs.getAppLogLines(). Cada registro do aplicativo apresenta a hora em que o registro foi gravado, a mensagem e o nível do registro.

Como gravar registros de aplicativos

O SDK Java do Google App Engine permite que um desenvolvedor registre os seguintes níveis de gravidade:

  • SEVERE
  • WARNING
  • INFO
  • CONFIG
  • FINE
  • FINER
  • FINEST

Os níveis de registro INFO, WARNING e SEVERE aparecem nos registros sem configuração extra. Para adicionar outros níveis de registro ao seu app Java, talvez seja necessário adicionar as propriedades adequadas do sistema ao arquivo appengine-web.xml do projeto e também modificar o arquivo logging.properties para definir o nível de registro que você quer. Esses dois arquivos são criados quando se inicia um projeto novo do App Engine Java usando o Maven. É possível encontrar esses arquivos nos seguintes locais:

Layout do projeto Maven

Para adicionar níveis de registro além de INFO, WARNING ou SEVERE, edite o arquivo appengine-web.xml para acrescentar o seguinte código dentro das tags <appengine-web-app>:

    <system-properties>
       <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
    </system-properties>

O nível de registro padrão em logging.properties é INFO. Para alterar o nível de registro padrão para todas as classes em seu aplicativo, edite o arquivo logging.properties. Por exemplo, você pode mudar .level = INFO para .level = WARNING.

No código do aplicativo, grave mensagens de registro usando java.util.logging.Logger API. O exemplo abaixo grava uma mensagem de registro de informações:

import java.util.logging.Logger;
//...

public class MyClass {

  private static final Logger log = Logger.getLogger(MyClass.class.getName());
  log.info("Your information log message.");
  //....

Quando seu aplicativo é executado, o App Engine registra as mensagens e as disponibiliza. É possível lê-los de maneira programática usando a Logs API conforme exibido a seguir ou navegar no Visualizador de registros. Você também pode fazer o download dos arquivos de registro usando a ferramenta AppCfg.

Como ler registros via API

O processo geral de recebimento de registros com a Logs API é o seguinte:

  1. Usar LogQuery para especificar quais registros retornar.
  2. Usar LogServiceFactory.getLogService() para criar o LogService
  3. Invocar LogServiceFactory.getLogService().fetch() para retornar um iterador para os registros de solicitação.
  4. Em cada iteração, para cada RequestLogs, processar as propriedades da solicitação conforme você quiser.
  5. Se quiser, use RequestLogs.getAppLogLines() para conseguir a lista de registros do aplicativo (AppLogLine) associados a essa solicitação.
  6. Se você recuperou a lista de registros do aplicativo, para cada AppLogLine, processe os dados da propriedade conforme quiser.

Código de amostra

Na amostra a seguir, apresentamos cinco registros de solicitação por vez com os registros de aplicativo deles. Isso permite que você percorra cada conjunto de registros usando um link Próximo.

Java 8

package com.example.appengine.logs;

import com.google.appengine.api.log.AppLogLine;
import com.google.appengine.api.log.LogQuery;
import com.google.appengine.api.log.LogServiceFactory;
import com.google.appengine.api.log.RequestLogs;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.joda.time.DateTime;

// Get request logs along with their app log lines and display them 5 at
// a time, using a Next link to cycle through to the next 5.
// With @WebServlet annotation the webapp/WEB-INF/web.xml is no longer required.
@WebServlet(
    name = "logs",
    description = "Logs: Display 5 lines of the request log",
    urlPatterns = "/logs"
)
public class LogsServlet extends HttpServlet {

  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {

    resp.setContentType("text/html");
    PrintWriter writer = resp.getWriter();
    writer.println("<!DOCTYPE html>");
    writer.println("<meta charset=\"utf-8\">");
    writer.println("<title>App Engine Logs Sample</title>");

    // We use this to break out of our iteration loop, limiting record
    // display to 5 request logs at a time.
    int limit = 5;

    // This retrieves the offset from the Next link upon user click.
    String offset = req.getParameter("offset");

    // We want the App logs for each request log
    LogQuery query = LogQuery.Builder.withDefaults();
    query.includeAppLogs(true);

    // Set the offset value retrieved from the Next link click.
    if (offset != null) {
      query.offset(offset);
    }

    // This gets filled from the last request log in the iteration
    String lastOffset = null;
    int count = 0;

    // Display a few properties of each request log.
    for (RequestLogs record : LogServiceFactory.getLogService().fetch(query)) {
      writer.println("<br>REQUEST LOG <br>");
      DateTime reqTime = new DateTime(record.getStartTimeUsec() / 1000);
      writer.println("IP: " + record.getIp() + "<br>");
      writer.println("Method: " + record.getMethod() + "<br>");
      writer.println("Resource " + record.getResource() + "<br>");
      writer.println(String.format("<br>Date: %s", reqTime.toString()));

      lastOffset = record.getOffset();

      // Display all the app logs for each request log.
      for (AppLogLine appLog : record.getAppLogLines()) {
        writer.println("<br>" + "APPLICATION LOG" + "<br>");
        DateTime appTime = new DateTime(appLog.getTimeUsec() / 1000);
        writer.println(String.format("<br>Date: %s", appTime.toString()));
        writer.println("<br>Level: " + appLog.getLogLevel() + "<br>");
        writer.println("Message: " + appLog.getLogMessage() + "<br> <br>");
      }

      if (++count >= limit) {
        break;
      }
    }

    // When the user clicks this link, the offset is processed in the
    // GET handler and used to cycle through to the next 5 request logs.
    writer.println(String.format("<br><a href=\"/?offset=%s\">Next</a>", lastOffset));
  }
}

Java 7

package com.example.appengine.logs;

import com.google.appengine.api.log.AppLogLine;
import com.google.appengine.api.log.LogQuery;
import com.google.appengine.api.log.LogServiceFactory;
import com.google.appengine.api.log.RequestLogs;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.joda.time.DateTime;

// Get request logs along with their app log lines and display them 5 at
// a time, using a Next link to cycle through to the next 5.
public class LogsServlet extends HttpServlet {
  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp)
         throws IOException {

    resp.setContentType("text/html");
    PrintWriter writer = resp.getWriter();
    writer.println("<!DOCTYPE html>");
    writer.println("<meta charset=\"utf-8\">");
    writer.println("<title>App Engine Logs Sample</title>");

    // We use this to break out of our iteration loop, limiting record
    // display to 5 request logs at a time.
    int limit = 5;

    // This retrieves the offset from the Next link upon user click.
    String offset = req.getParameter("offset");

    // We want the App logs for each request log
    LogQuery query = LogQuery.Builder.withDefaults();
    query.includeAppLogs(true);

    // Set the offset value retrieved from the Next link click.
    if (offset != null) {
      query.offset(offset);
    }

    // This gets filled from the last request log in the iteration
    String lastOffset = null;
    int count = 0;

    // Display a few properties of each request log.
    for (RequestLogs record : LogServiceFactory.getLogService().fetch(query)) {
      writer.println("<br>REQUEST LOG <br>");
      DateTime reqTime = new DateTime(record.getStartTimeUsec() / 1000);
      writer.println("IP: " + record.getIp() + "<br>");
      writer.println("Method: " + record.getMethod() + "<br>");
      writer.println("Resource " + record.getResource() + "<br>");
      writer.println(String.format("<br>Date: %s", reqTime.toString()));

      lastOffset = record.getOffset();

      // Display all the app logs for each request log.
      for (AppLogLine appLog : record.getAppLogLines()) {
        writer.println("<br>" + "APPLICATION LOG" + "<br>");
        DateTime appTime = new DateTime(appLog.getTimeUsec() / 1000);
        writer.println(String.format("<br>Date: %s", appTime.toString()));
        writer.println("<br>Level: " + appLog.getLogLevel() + "<br>");
        writer.println("Message: " + appLog.getLogMessage() + "<br> <br>");
      }

      if (++count >= limit) {
        break;
      }
    }

    // When the user clicks this link, the offset is processed in the
    // GET handler and used to cycle through to the next 5 request logs.
    writer.println(String.format("<br><a href=\"/?offset=%s\">Next</a>", lastOffset));
  }
}

Na amostra, observe que o gerenciador GET espera ser invocado novamente pelo usuário clicando no link "Próximo". Assim, ele extrai o parâmetro "offset", se estiver presente. Esse "offset" é usado na nova chamada posterior de LogServiceFactory.getLogService().fetch() para “percorrer” cada grupo de cinco registros de solicitação. Não há nada de especial no número cinco, pode ser qualquer outro.

Formato de URL de registro no console do Google Cloud Platform

Consulte a seguinte amostra de URL para um exemplo do formato de URL de registro no console do GCP:

https://console.cloud.google.com/logs?filters=request_id:000000db00ff00ff827e493472570001737e73686966746361727331000168656164000100

Como ler registros no console

Para visualizar os registros usando o Visualizador:

  1. No console do GCP, acesse os registros do projeto.

  2. Use o filtro escolhido para recuperar os registros que você quer ver. Filtre por várias combinações de tempo, nível de registro, módulo e por marcador ou expressão regular.

    Os marcadores são expressões regulares que servem para filtrar os registros por campos. Os marcadores válidos são estes:

    • day
    • month
    • year
    • hour
    • minute
    • second
    • tzone
    • remotehost
    • identd_user
    • user
    • status
    • bytes
    • referrer
    • useragent
    • method
    • path
    • querystring
    • protocolo
    • request_id

    Por exemplo, path:/foo.* useragent:.*Chrome.* recebe registros de todas as solicitações para um caminho que começa com /foo, emitido por um navegador Chrome.

Um registro comum do App Engine contém dados no formato de registro combinado Apache, junto com alguns campos especiais do App Engine, conforme exibido no seguinte registro de amostra:

192.0.2.0 - test [27/Jun/2014:09:11:47 -0700] "GET / HTTP/1.1" 200 414
"http://www.example.com/index.html"
Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36"
"1-dot-calm-sylph-602.appspot.com" ms=195 cpu_ms=42 cpm_usd=0.000046
loading_request=1 instance=00c61b117cfeb66f973d7df1b7f4ae1f064d app_engine_release=1.9.63

Como entender os campos de registros de solicitação

A tabela a seguir lista os campos em ordem de ocorrência com uma descrição:

Ordem do campo Nome do campo Sempre presente? Descrição
1 Client address Sim Endereço IP do cliente. Exemplo: 192.0.2.0.
2 RFC1413 identity Não Identidade RFC1413 do cliente. É quase sempre o caractere -.
3 User Não Presente somente se o aplicativo usar a Users API e o usuário tiver feito login. Esse valor corresponde ao "apelido" da Conta do Google. Por exemplo, se a conta for test@example.com, o apelido que fez login neste campo é test.
4 Timestamp Sim Solicita o carimbo de data/hora. Exemplo: [27/Jun/2014:09:11:47 -0700].
5 Request querystring Sim Primeira linha da solicitação, contendo método, caminho e versão HTTP. Exemplo: GET / HTTP/1.1.
6 HTTP Status Code Sim Código de status HTTP retornado. Exemplo: 200.
7 Response size Sim Tamanho da resposta em bytes. Exemplo: 414.
8 Referrer path Não Se não houver indicação, o registro não terá caminho, mas apenas -. Exemplo de caminho de indicação: "http://www.example.com/index.html".
9 User-agent Sim Identifica o navegador e o sistema operacional para o servidor da Web. Exemplo: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36.
10 Hostname Sim O nome do host usado pelo cliente para se conectar ao aplicativo App Engine. Exemplo: (1-dot-calm-sylph-602.appspot.com)
11 Wallclock time Sim Tempo total em milissegundos gasto pelo App Engine na solicitação. Essa duração não inclui o tempo gasto entre o cliente e o servidor que executa a instância do aplicativo. Exemplo: ms=195.
12 CPU milliseconds Sim Milissegundos da CPU necessários para cumprir a solicitação. Essa é a quantidade de milissegundos gasta pela CPU que de fato executa o código do aplicativo, considerando uma Intel x86 de 1,2 GHz. Se a CPU utilizada for mais rápida do que a base, poderá haver mais milissegundos do que o tempo real definido acima. Exemplo: cpu_ms=42.
13 Exit code Não Presente apenas se a instância for desligada depois de receber a solicitação. No formato exit_code=XXX, em que XXX é um número de três dígitos correspondente ao motivo pela qual a instância foi desligada. Os códigos de saída não são documentados, já que se destinam a ajudar o Google a detectar e corrigir problemas.
14 Estimated cost Sim OBSOLETO. Custo estimado de mil solicitações como esta, em US$. Exemplo: cpm_usd=0.000046.
15 Queue name Não O nome da fila de tarefas usado. Presente apenas se a solicitação usar uma fila de tarefas. Exemplo: queue_name=default.
16 Task name Não O nome da tarefa executada na fila de tarefas para essa solicitação. Presente somente se a solicitação resultou na fila de uma tarefa. Exemplo: task_name=7287390692361099748.
17 Pending queue Não Presente somente se uma solicitação demorar algum tempo em uma fila pendente. Se houver vários assim nos registros e/ou se os valores forem altos, pode ser uma indicação de que você precisa de mais instâncias para atender seu tráfego. Exemplo: pending_ms=195.
18 Loading request Não Presente somente se a solicitação for de carregamento. Isso significa que uma instância precisou ser iniciada. De preferência, é necessário que as instâncias sejam adequadas e íntegras durante o máximo de tempo possível, atendendo a um grande número de solicitações antes de serem recicladas e precisarem ser iniciadas outra vez. Isso significa que você não verá muitos assim nos registros. Exemplo: loading_request=1.
19 Instance Sim Identificador exclusivo para a instância que gerencia a solicitação. Exemplo: instance=00c61b117cfeb66f973d7df1b7f4ae1f064d.
20 Version Sim A versão de lançamento atual do App Engine usada na produção: 1.9.63.

Cotas e limites

O aplicativo é afetado pelas seguintes cotas relacionadas aos registros:

  • dados de registros recuperados por meio da Logs API
  • cota de processamento de registro e retenção

Cota para dados recuperados

Os primeiros 100 megabytes de dados de registros recuperados por dia, por meio das chamadas da Logs API, são gratuitos. Depois que essa quantia for excedida, nenhuma outra chamada da Logs API será bem-sucedida, a menos que o faturamento esteja ativado em seu aplicativo. Se estiver, os dados acima de 100 megabytes resultam em US$ 0,12/GB.

Cota de processamento de registros

A geração de registros para aplicativos do App Engine é fornecida pelo Stackdriver. Por padrão, os registros são armazenados gratuitamente para os aplicativos por até 7 dias e 5 GB. Os registros anteriores ao tempo de retenção máximo são excluídos, e as tentativas de armazenamento acima do limite gratuito resultarão em erro. Você pode atualizar para o nível Premium para ter mais capacidade de armazenamento e de retenção. Consulte o preço do Stackdriver para mais informações sobre limites e taxas de geração de registro. Se quiser manter os registros por mais tempo do que o Stackdriver permite, você pode exportar os registros para o Google Cloud Storage, o Google BigQuery ou o Google Cloud Pub/Sub.

Esta página foi útil? Conte sua opinião sobre:

Enviar comentários sobre…

Ambiente padrão do App Engine para Java