Ativar o log no formato JSON na imagem docker do JBoss EAP 6.4. Também vamos mudar o formato que o stacktrace aparece no JSON.

Imagem oficial

As imagens docker oficiais podem ser obtidas no registry da RedHat, em registry.redhat.io.

# login
docker login -u USER registry.redhat.io

# listar as tags disponíveis
skopeo list-tags docker://registry.redhat.io/jboss-eap-6/eap64-openshift

Ativando o log JSON

Para ativar o log do JBoss no formato JSON, basta subir um container setando a variável de ambiente ENABLE_JSON_LOGGING para true.

# executar
docker run --rm \
  --env ENABLE_JSON_LOGGING=true \
  registry.redhat.io/jboss-eap-6/eap64-openshift:1.9

# ou se você quiser ver o JSON formatado
jq -R '. as $raw | try fromjson catch $raw' <(\
  docker run --rm \
    --env ENABLE_JSON_LOGGING=true \
    registry.redhat.io/jboss-eap-6/eap64-openshift:1.9 \
)

A partir disso cada linha do log da aplicação será parecida com isso:

{
  "@version": 1,
  "@timestamp": "2020-11-20T14:20:41-03:00",
  "sequence": 7229,
  "loggerClassName": "org.jboss.as.server.ServerLogger_$logger",
  "loggerName": "org.jboss.as",
  "level": "INFO",
  "message": "JBAS015874: JBoss EAP 6.4.20.GA (AS 7.5.20.Final-redhat-1) iniciado em 65151ms - Iniciado 16089 de serviços 16123 (os serviços 121 são lazy, passivos ou em demanda)",
  "threadName": "Controller Boot Thread",
  "threadId": 19,
  "mdc": {},
  "ndc": "",
  "log-handler": "CONSOLE"
}

Se houver exceção, o log será parecido com isso:

Clique AQUI para visualizar o log em JSON
{
  "@version": 1,
  "@timestamp": "2020-11-20T16:24:35-03:00",
  "sequence": 4998,
  "loggerClassName": "org.jboss.jca.core.CoreLogger_$logger",
  "loggerName": "org.jboss.jca.core.connectionmanager.pool.strategy.OnePool",
  "level": "WARN",
  "message": "IJ000604: Throwable while attempting to get a new connection: null",
  "threadName": "ServerService Thread Pool -- 62",
  "threadId": 126,
  "mdc": {},
  "ndc": "",
  "exception": {
    "refId": 1,
    "exceptionType": "javax.resource.ResourceException",
    "message": "Could not create connection",
    "frames": [
      {
        "class": "org.jboss.jca.adapters.jdbc.local.LocalManagedConnectionFactory",
        "method": "getLocalManagedConnection",
        "line": 351
      },
      {
        "class": "org.jboss.jca.adapters.jdbc.local.LocalManagedConnectionFactory",
        "method": "createManagedConnection",
        "line": 299
      },
      {
        "class": "org.jboss.jca.core.connectionmanager.pool.mcp.SemaphoreArrayListManagedConnectionPool",
        "method": "createConnectionEventListener",
        "line": 874
      },
      {
        "class": "org.jboss.jca.core.connectionmanager.pool.mcp.SemaphoreArrayListManagedConnectionPool",
        "method": "getConnection",
        "line": 416
      },
      {
        "class": "org.jboss.jca.core.connectionmanager.pool.AbstractPool",
        "method": "getSimpleConnection",
        "line": 479
      },
      {
        "class": "org.jboss.jca.core.connectionmanager.pool.AbstractPool",
        "method": "getConnection",
        "line": 451
      },
      {
        "class": "org.jboss.jca.core.connectionmanager.AbstractConnectionManager",
        "method": "getManagedConnection",
        "line": 344
      },
      {
        "class": "org.jboss.jca.core.connectionmanager.tx.TxConnectionManagerImpl",
        "method": "getManagedConnection",
        "line": 367
      },
      {
        "class": "org.jboss.jca.core.connectionmanager.AbstractConnectionManager",
        "method": "allocateConnection",
        "line": 499
      },
      {
        "class": "org.jboss.jca.adapters.jdbc.WrapperDataSource",
        "method": "getConnection",
        "line": 143
      },
      {
        "class": "org.jboss.as.connector.subsystems.datasources.WildFlyDataSource",
        "method": "getConnection",
        "line": 69
      },
      {
        "class": "org.hibernate.ejb.connection.InjectedDataSourceConnectionProvider",
        "method": "getConnection",
        "line": 71
      },
      {
        "class": "org.hibernate.cfg.SettingsFactory",
        "method": "buildSettings",
        "line": 113
      },
      {
        "class": "org.hibernate.cfg.Configuration",
        "method": "buildSettingsInternal",
        "line": 2863
      },
      {
        "class": "org.hibernate.cfg.Configuration",
        "method": "buildSettings",
        "line": 2859
      },
      {
        "class": "org.hibernate.cfg.Configuration",
        "method": "buildSessionFactory",
        "line": 1870
      },
      {
        "class": "org.hibernate.ejb.Ejb3Configuration",
        "method": "buildEntityManagerFactory",
        "line": 906
      },
      {
        "class": "org.hibernate.ejb.HibernatePersistence",
        "method": "createContainerEntityManagerFactory",
        "line": 74
      },
      {
        "class": "org.jboss.as.jpa.service.PersistenceUnitServiceImpl",
        "method": "createContainerEntityManagerFactory",
        "line": 226
      },
      {
        "class": "org.jboss.as.jpa.service.PersistenceUnitServiceImpl",
        "method": "access$700",
        "line": 59
      },
      {
        "class": "org.jboss.as.jpa.service.PersistenceUnitServiceImpl$1",
        "method": "run",
        "line": 107
      },
      {
        "class": "java.util.concurrent.ThreadPoolExecutor",
        "method": "runWorker",
        "line": 1149
      },
      {
        "class": "java.util.concurrent.ThreadPoolExecutor$Worker",
        "method": "run",
        "line": 624
      },
      {
        "class": "java.lang.Thread",
        "method": "run",
        "line": 748
      },
      {
        "class": "org.jboss.threads.JBossThread",
        "method": "run",
        "line": 122
      }
    ],
    "causedBy": {
      "exception": {
        "refId": 2,
        "exceptionType": "java.sql.SQLException",
        "message": "ORA-01017: invalid username/password; logon denied\n",
        "frames": [
          {
            "class": "oracle.jdbc.driver.T4CTTIoer11",
            "method": "processError",
            "line": 494
          },
          {
            "class": "oracle.jdbc.driver.T4CTTIoer11",
            "method": "processError",
            "line": 441
          },
          {
            "class": "oracle.jdbc.driver.T4CTTIoer11",
            "method": "processError",
            "line": 436
          },
          {
            "class": "oracle.jdbc.driver.T4CTTIfun",
            "method": "processError",
            "line": 1061
          },
          {
            "class": "oracle.jdbc.driver.T4CTTIoauthenticate",
            "method": "processError",
            "line": 550
          },
          {
            "class": "oracle.jdbc.driver.T4CTTIfun",
            "method": "receive",
            "line": 623
          },
          {
            "class": "oracle.jdbc.driver.T4CTTIfun",
            "method": "doRPC",
            "line": 252
          },
          {
            "class": "oracle.jdbc.driver.T4CTTIoauthenticate",
            "method": "doOAUTH",
            "line": 499
          },
          {
            "class": "oracle.jdbc.driver.T4CTTIoauthenticate",
            "method": "doOAUTH",
            "line": 1279
          },
          {
            "class": "oracle.jdbc.driver.T4CConnection",
            "method": "logon",
            "line": 663
          },
          {
            "class": "oracle.jdbc.driver.PhysicalConnection",
            "method": "connect",
            "line": 688
          },
          {
            "class": "oracle.jdbc.driver.T4CDriverExtension",
            "method": "getConnection",
            "line": 39
          },
          {
            "class": "oracle.jdbc.driver.OracleDriver",
            "method": "connect",
            "line": 691
          },
          {
            "class": "org.jboss.jca.adapters.jdbc.local.LocalManagedConnectionFactory",
            "method": "getLocalManagedConnection",
            "line": 323
          },
          {
            "class": "org.jboss.jca.adapters.jdbc.local.LocalManagedConnectionFactory",
            "method": "createManagedConnection",
            "line": 299
          },
          {
            "class": "org.jboss.jca.core.connectionmanager.pool.mcp.SemaphoreArrayListManagedConnectionPool",
            "method": "createConnectionEventListener",
            "line": 874
          },
          {
            "class": "org.jboss.jca.core.connectionmanager.pool.mcp.SemaphoreArrayListManagedConnectionPool",
            "method": "getConnection",
            "line": 416
          },
          {
            "class": "org.jboss.jca.core.connectionmanager.pool.AbstractPool",
            "method": "getSimpleConnection",
            "line": 479
          },
          {
            "class": "org.jboss.jca.core.connectionmanager.pool.AbstractPool",
            "method": "getConnection",
            "line": 451
          },
          {
            "class": "org.jboss.jca.core.connectionmanager.AbstractConnectionManager",
            "method": "getManagedConnection",
            "line": 344
          },
          {
            "class": "org.jboss.jca.core.connectionmanager.tx.TxConnectionManagerImpl",
            "method": "getManagedConnection",
            "line": 367
          },
          {
            "class": "org.jboss.jca.core.connectionmanager.AbstractConnectionManager",
            "method": "allocateConnection",
            "line": 499
          },
          {
            "class": "org.jboss.jca.adapters.jdbc.WrapperDataSource",
            "method": "getConnection",
            "line": 143
          },
          {
            "class": "org.jboss.as.connector.subsystems.datasources.WildFlyDataSource",
            "method": "getConnection",
            "line": 69
          },
          {
            "class": "org.hibernate.ejb.connection.InjectedDataSourceConnectionProvider",
            "method": "getConnection",
            "line": 71
          },
          {
            "class": "org.hibernate.cfg.SettingsFactory",
            "method": "buildSettings",
            "line": 113
          },
          {
            "class": "org.hibernate.cfg.Configuration",
            "method": "buildSettingsInternal",
            "line": 2863
          },
          {
            "class": "org.hibernate.cfg.Configuration",
            "method": "buildSettings",
            "line": 2859
          },
          {
            "class": "org.hibernate.cfg.Configuration",
            "method": "buildSessionFactory",
            "line": 1870
          },
          {
            "class": "org.hibernate.ejb.Ejb3Configuration",
            "method": "buildEntityManagerFactory",
            "line": 906
          },
          {
            "class": "org.hibernate.ejb.HibernatePersistence",
            "method": "createContainerEntityManagerFactory",
            "line": 74
          },
          {
            "class": "org.jboss.as.jpa.service.PersistenceUnitServiceImpl",
            "method": "createContainerEntityManagerFactory",
            "line": 226
          },
          {
            "class": "org.jboss.as.jpa.service.PersistenceUnitServiceImpl",
            "method": "access$700",
            "line": 59
          },
          {
            "class": "org.jboss.as.jpa.service.PersistenceUnitServiceImpl$1",
            "method": "run",
            "line": 107
          },
          {
            "class": "java.util.concurrent.ThreadPoolExecutor",
            "method": "runWorker",
            "line": 1149
          },
          {
            "class": "java.util.concurrent.ThreadPoolExecutor$Worker",
            "method": "run",
            "line": 624
          },
          {
            "class": "java.lang.Thread",
            "method": "run",
            "line": 748
          },
          {
            "class": "org.jboss.threads.JBossThread",
            "method": "run",
            "line": 122
          }
        ]
      }
    }
  },
  "log-handler": "CONSOLE"
}

📋 Observar que cada linha do stacktrace ficou em um fragmento de JSON, o que pode deixar um pouco chato a visualização.

Podemos configurar para que o stacktrace da exception fique no formato padrão, de quando visualizamos no console.

Formato do stacktrace da exception no log

Vamos mudar para o formato tradicional, algo parecido com:

org.jboss.msc.service.StartException in service jboss.web.deployment.default-host./xxx: org.jboss.msc.service.StartException in anonymous service: JBAS018040: Falha ao iniciar o contexto
    at org.jboss.as.web.deployment.WebDeploymentService$1.run(WebDeploymentService.java:99)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
    at org.jboss.threads.JBossThread.run(JBossThread.java:122)
Caused by: org.jboss.msc.service.StartException in anonymous service: JBAS018040: Falha ao iniciar o contexto
    at org.jboss.as.web.deployment.WebDeploymentService.doStart(WebDeploymentService.java:168)
    at org.jboss.as.web.deployment.WebDeploymentService.access$000(WebDeploymentService.java:61)
    at org.jboss.as.web.deployment.WebDeploymentService$1.run(WebDeploymentService.java:96)
    ... 6 more

Não é possível simplesmente por meio de um parâmetro da imagem. Precisamos alterar o arquivo /opt/eap/standalone/configuration/logging.properties para configurar o formatter OPENSHIFT do log4j.

Visualizar o /opt/eap/standalone/configuration/logging.properties e observar a configuração do formatter.OPENSHIFT:

docker run --rm \
  registry.redhat.io/jboss-eap-6/eap64-openshift:1.9 \
  cat -n /opt/eap/standalone/configuration/logging.properties

Agora vamos criar um Dockerfile conforme abaixo, que vai configurar o formatter.OPENSHIFT para printar o stacktrace no formato que desejamos:

FROM registry.redhat.io/jboss-eap-6/eap64-openshift:1.9

# backup do arquivo original
RUN cp -a /opt/eap/standalone/configuration/logging.properties \
          /opt/eap/standalone/configuration/logging.properties.original

# adiciona a propriedade exceptionOutputType
RUN sed -i -E \
      's|^formatter.OPENSHIFT.properties=metaData$|formatter.OPENSHIFT.properties=metaData,exceptionOutputType|' \
      /opt/eap/standalone/configuration/logging.properties

# define exceptionOutputType=FORMATTED (default: DETAILED)
RUN echo -e \
      '\nformatter.OPENSHIFT.exceptionOutputType=FORMATTED' >> \
      /opt/eap/standalone/configuration/logging.properties

📋 As três linhas de RUN podem ficar em um único RUN. Estão separadas para melhorar a visualização.

Com o Dockerfile já criado, vamos construir a imagem:

docker build -t jboss:json .

Agora é só executar como já vimos anteriormente:

# executar
docker run --rm \
  --env ENABLE_JSON_LOGGING=true \
  jboss:json

# ou se você quiser ver o JSON formatado
jq -R '. as $raw | try fromjson catch $raw' <(\
  docker run --rm \
    --env ENABLE_JSON_LOGGING=true \
    jboss:json \
)

E se houver uma exceção, o log com o stacktrace será algo parecido com:

{
  "@version": 1,
  "@timestamp": "2020-11-21T11:18:28-03:00",
  "sequence": 7086,
  "loggerClassName": "org.jboss.msc.service.ServiceLogger_$logger",
  "loggerName": "org.jboss.msc.service.fail",
  "level": "ERROR",
  "message": "MSC000001: Failed to start service jboss.web.deployment.default-host./xxx",
  "threadName": "ServerService Thread Pool -- 107",
  "threadId": 237,
  "mdc": {},
  "ndc": "",
  "stackTrace": "org.jboss.msc.service.StartException in service jboss.web.deployment.default-host./xxx: org.jboss.msc.service.StartException in anonymous service: JBAS018040: Falha ao iniciar o contexto\n\tat org.jboss.as.web.deployment.WebDeploymentService$1.run(WebDeploymentService.java:99)\n\tat java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)\n\tat java.util.concurrent.FutureTask.run(FutureTask.java:266)\n\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)\n\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)\n\tat java.lang.Thread.run(Thread.java:748)\n\tat org.jboss.threads.JBossThread.run(JBossThread.java:122)\nCaused by: org.jboss.msc.service.StartException in anonymous service: JBAS018040: Falha ao iniciar o contexto\n\tat org.jboss.as.web.deployment.WebDeploymentService.doStart(WebDeploymentService.java:168)\n\tat org.jboss.as.web.deployment.WebDeploymentService.access$000(WebDeploymentService.java:61)\n\tat org.jboss.as.web.deployment.WebDeploymentService$1.run(WebDeploymentService.java:96)\n\t... 6 more\n",
  "log-handler": "CONSOLE"
}

📋 Agora todo o stacktrace é um único atributo no json.

Uma ferramenta como o Kibana vai mostrar o log acima formatado, algo parecido com:

Kibana - Stacktrace formatado

Ou você pode salvar o log acima para um arquivo e ver formatado, assim:

while read -r line; do echo -e $line; done <log.json

Referências