Documentação do Job Logs Monitor

Visão Geral

O serviço Job Logs Monitor permite monitorar logs de execução de jobs.

Guia de Instalação

Nesta seção são descritos os requisitos, os passos para instalação e configuração do serviço Job Logs Monitor.

Requisitos

Para a execução do serviço Job Logs Monitor são necessárias as seguintes dependências:

  • Docker

  • Acesso de leitura ao diretório contendo os arquivos de logs

Instalação

O serviço Job Logs Monitor é executado via Docker, então é necessário que a imagem seja transferida para a máquina de execução. Ela está no repositório repo.tecgraf.puc-rio.br:18089 e no caminho soma/soma-job-logs-monitor. Assim para pegar a versão x.y.z o seguinte comando deve ser usado:

$ docker pull repo.tecgraf.puc-rio.br:18089/soma/soma-job-logs-monitor:x.y.z

Configuração

Algumas configurações são necessárias para executar o serviço.

Volumes

O seguinte volume deve ser definido e mapedo:

/job_logs_root

Diretório raiz onde estão os arquivos de logs das execuções dos jobs.

Variáveis de ambiente

As seguintes variáveis de ambiente devem ser definidas para a execução do serviço:

JOB_LOGS_MONITOR_SERVICES_PROJECTSERVICE_CLIENTBASEURL

URL para o servidor REST que será utilizado para verificar a permissão de acesso do usuário ao projeto. Valor de exemplo http://localhost:8010/v1.

JOB_LOGS_MONITOR_PROJECT_PERMISSION_CHECK_ENABLE

Flag para ativar/desativar a verificação de permissão de acesso do usuário ao projeto. Valor padrão é true.

JOB_LOGS_MONITOR_SERVICES_JOBSERVICE_CLIENTBASEURL

URL para o serviço que será utilizado para verificar se o job e o arquivo de log existem. Valor de exemplo http://localhost:8089/v1.

JOB_LOGS__MONITOR_LOGSDIR_PATTERN

Padrão do caminho usado para localizar os arquivos de logs dos jobs. Os seguintes marcadores devem ser utilizados:

__ROOT__

diretório raiz onde estão os arquivos de logs (o caminho deve sempre ter esse item no início)

__PROJECT__

identificador do projeto

__JOB__

identificador do job

Pode-se utilizar também texto para complementar o caminho. Por exemplo __ROOT__/__PROJECT__/.cmds/__JOB__/logs.

Já as variáveis a seguir são opcionais e podem ser definidas para alterar a configuração padrão:

JOB_LOGS_MONITOR_WATCHER_THREAD_POOL_SIZE

Tamanho do pool de threads responsável por processar os eventos de alteração de arquivos. Cada diretório monitorado tem o seu pool particular. O valor padrão para o tamanho do pool é 4.

JOB_LOGS_MONITOR_READER_MAX_LENGTH_SIZE

Tamanho padrão máximo, em bytes, do trecho do arquivo de log que será lido, em cada evento de alteração no arquivo. O valor padrão é 10485760 (10 MB).

JOB_LOGS_MONITOR_READER_CHARSET_DEFAULT

Charset padrão usado para ler o conteúdo de arquivos de log quando a heurística de identificação do charset falhar/tiver baixa confiança. Valor padrão é ISO-8859-1.

JOB_LOGS_MONITOR_WEBSOCKET_CORS_ORIGINS

Lista de origens – separadas por vírgula – aceitas usada pelo filtro CORS. Por exemplo http://origin.com,http://www.origin.io. O valor padrão é *, indicando que qualquer origem é aceita.

Portas

A seguinte portas devem ser exportadas:

8090

Porta para acesso ao endpoint do serviço websocket.

Configurações avançadas

As seguintes variáveis de ambiente podem ser usadas para fazer configurações avançadas:

JOB_LOGS_MONITOR_WEBSOCKET_PATH

Define o caminho da URL de acesso ao serviço. O valor padrão é /v1/job_logs_monitor

Execução

Para executar deve-se usar o comando docker run com as seguintes opções:

--user user_id:group_id

Executar informando o UID e GID do usuário que irá executar o serviço. Ele deve ter acesso de leitura ao diretório raiz onde estão dos arquivos de logs das execuções de jobs.

--restart=unless-stopped

Define a política de reinício do container Docker. O valor unless-stopped indica que o container deve ser reiniciado sempre, menos se ele foi parado antes do daemon Docker ter sido parado. Mais detalhes em Docker run reference:Restart policies.

e juntamente com a configuração definida. Segue exemplo de script para iniciar um container Docker usando a imagem do serviço Job Logs Monitor:

#!/bin/bash

REPO=repo.tecgraf.puc-rio.br:18089
IMAGE=soma/soma-job-logs-monitor
VERSION=x.y.z

  WORKING_DIR=$(dirname "$PWD")
CONTAINER_NAME=soma-job-logs-monitor-${VERSION}

function start() {
  docker run -d \
    --name ${CONTAINER_NAME} \
    -p 8090:8090 \
    -v "/path/to/logs/root":/job_logs_root \
    -e "JOB_LOGS_MONITOR_SERVICES_PROJECTSERVICE_CLIENTBASEURL=http://server:8010/v1" \
    -e "JOB_LOGS_MONITOR_SERVICES_JOBSERVICE_CLIENTBASEURL=http://server:8089/v1" \
    -e "JOB_LOGS__MONITOR_LOGSDIR_PATTERN=__ROOT__/__PROJECT__/.cmds/__JOB__/logs" \
    --restart=unless-stopped \
    --user "$(id -u "${USER}")":"$(id -g "${USER}")" \
  ${REPO}/${IMAGE}:${VERSION}
}

function stop() {
  docker stop ${CONTAINER_NAME}
}

case "$1" in
  start)
    start
    ;;
  stop)
    stop
    ;;
  *)
    echo "Usage: ${0} {start|stop}"
    ;;
esac

Monitoramento

O serviço possui os endpoints de /healthz, /healthz/readiness e /healthz/liveness para monitorar o estado do serviço:

Liveness

Verifica se o serviço está ativo. A URL de acesso é http://<host>:<port>/healthz/liveness. Segue retorno esperado:

{
  "status":"UP"
}

Readiness

Verifica se o serviço está pronto para receber requisições. A URL de acesso é http://<host>:<port>/healthz/readiness. Segue retorno esperado:

{
  "status":"UP"
}

Healthz

Combina o retorno dos endpoints liveness e readiness. A URL de acesso é http://<host>:<port>/healthz. Segue o retorno esperado:

{
  "status":"UP",
  "components":{
    "livenessState":{
      "status":"UP"
    },
    "ping":{
      "status":"UP"
    },
    "readinessState":{
      "status":"UP"
    }
  },
  "groups":[
    "liveness",
    "readiness"
  ]
}

API de Monitoração

A monitoração para captura das alterações dos arquivos de log pode ser feita observando os arquivos de log de interesse através da API do serviço Job Logs Monitor.

Inscrição para Notificação

Para receber as notificações das alterações, é necessário enviar uma mensagem com o comando SUBSCRIBE e o tópico de interesse. O tópico de logs de jobs é o jobLogs e deve conter o projeto, o job, o nó do fluxo e o nome do arquivo. Além disso, a mensagem deve ter o seqnum para indicar o “momento” do início das notificações. Segue a especificação da mensagem:

Message:
  type: object
  required:
    - command
    - subscriptionId
    - topic
  properties:
    command:
      $ref: "#/components/schemas/Command"
    subscriptionId:
      description: ID of the subscription (generated by the client).
      type: string
    topic:
      oneOf:
        - $ref: '#/components/schemas/Topic'
        discriminator:
        propertyName: topicType
        mapping:
            jobLogs: '#/components/schemas/JobLogTopic'
    seqnum:
        description: it’s a seqnum indicating the last state known to the client, thus only changes after the moment indicated by this seqnum shall be notified.
      type: integer
      format: int64
Command:
  type: string
  enum:
    - SUBSCRIBE
    - UNSUBSCRIBE
TopicType:
  type: string
  enum:
    - jobLogs
Topic:
  type: object
  discriminator:
    propertyName: topicType
  required:
    - topicType
  properties:
    topicType:
        $ref: "#/components/schemas/TopicType"
JobLogTopic:
  type: object
  required:
    - projectId
    - jobId
  properties:
    topicType:
      description: Type of the topic to subscribe or unsubscribe.
      $ref: "#/components/schemas/TopicType"
    projectId:
      description: ID of the project which the job logs to be watched.
      type: string
    jobId:
      description: ID of the job or group to be watched.
      type: string
    flowNodeId:
      description: ID of the flow node to be watched.
      type: integer
    logName:
      description: name of the log to be watched.
      type: string
    encoding:
      description: optional encoding to be used to read the monitored log file. The supported encodes are US-ASCII, ISO-8859-1 e UTF-8. If an unsupported, an invalid or a null values is informed the default encoding will be used.
      type: string

Exemplo de mensagem de SUBSCRIBE:

{
  "command":"SUBSCRIBE",
  "subscriptionId":"subId1",
  "topic":{
    "topicType":"jobLogs",
    "projectId":"dXNlci9Qcm9qZWN0",
    "jobId":"dXNlckBQcm9qLkVCT1A2SDIyTjU=",
    "flowNodeId":0,
    "logName":"log.out",
    "encoding":"UTF-8"
  },
  "seqnum":0
}

Exemplo de mensagem de UNSUBSCRIBE:

{
  "command":"UNSUBSCRIBE",
  "subscriptionId":"subId1",
  "topic":{
    "topicType":"jobLogs",
    "projectId":"dXNlci9Qcm9qZWN0",
    "jobId":"dXNlckBQcm9qLkVCT1A2SDIyTjU=",
    "flowNodeId":0,
    "logName":"log.out"
  }
}

Notificação de Alteração

Alterações nos arquivos de log são informadas através da mensagem Notification que contém o evento da notificação. Este evento contém as seguintes informações:

jobId

identificador do job

projectId

identificador do projeto

logName

nome do arquivo de log

chunks

array de chunk de dados – atualmente esse array tem apenas um elemento. Cada chunk é referente a um trecho dos dados enviados e possuem as seguintes informações:

offset

deslocamento, a partir do início do arquivo de log, referente ao chunk (em bytes)

length

tamanho referente ao chunk (em bytes)

data

trecho de log enviado

offset

deslocamento, a partir do início do arquivo de log, referente aos dados enviados (em bytes)

length

tamanho dos dados enviados (em bytes)

totalSize

tamanho total do arquivo de log (em bytes)

fileEncoding

encoding usado para ler o arquivo

Segue a especificação da mensagem de notificação:

Notification:
  type: object
  required:
    - subscriptionId
    - topicType
    - event
  properties:
    subscriptionId:
      type: string
    topicType:
      $ref: "#/components/schemas/TopicType"
    topicEvent:
      oneOf:
        - $ref: '#/components/schemas/TopicEvent'
    discriminator:
      propertyName: topicEventType
      mapping:
        jobLogsEvent: '#/components/schemas/JobLogsEventTopic'
TopicEventType:
  type: string
  enum:
    - jobLogsEvent
  TopicEvent:
    type: object
    discriminator:
      propertyName: topicEventType
    required:
        - topicEventType
    properties:
        topicEventType:
          $ref: "#/components/schemas/TopicEventType"
  JobLogsEventTopic:
    type: object
    required:
    - projectId
    - jobId
    - flowNodeId
    - logName
    - seqnum
    - offset
    - length
    - fileEncoding
    - chunks
    properties:
    topicType:
      description: Type of the topic to subscribe or unsubscribe.
      $ref: "#/components/schemas/TopicType"
    projectId:
      description: ID of the project which the job logs to be watched.
      type: string
    jobId:
      description: ID of the job or group to be watched.
      type: string
    flowNodeId:
      description: ID of the flow node to be watched.
      type: integer
    logName:
      description: name of the log to be watched.
      type: string
    seqnum:
      type: integer
      format: int64
    offset:
      type: integer
      format: int64
    length:
      type: integer
    totalSize:
      type: integer
      format: int64
    fileEncoding:
      description: encoding used to read the file
      type: string
    chunks:
      type: array
      items:
        $ref: "#/components/schemas/DataChunk"
DataChunk:
  type: object
  required:
    - offset
    - length
    - data
  properties:
      offset:
      type: integer
      format: int64
      length:
      type: integer
      data:
      type: string

Segue exemplo de notificação retornada no formato JSON :

{
  "subscriptionId": "subId1",
  "topicType": "jobLogs",
  "topicEvent": {
    "topicEventType": "jobLogsEvent",
    "projectId": "user/Project",
    "jobId": "user@Proj.EBOP6H22N5",
    "flowNodeId": 0,
    "logName": "log.out",
    "seqnum": 53,
    "offset": 32,
    "length": 21,
    "totalSize": 53,
    "fileEncoding": "UTF-8",
    "chunks": [
      {
        "offset": 32,
        "length": 10,
        "data": "A"
      },
      {
        "offset": 42,
        "length": 11,
        "data": "B"
      }
    ]
    }
}

Notificações de Erros

O serviço de monitoração enviará também mensagens de erro. As mensagens de erro seguem a seguinte especificação:

NotificationError:
  type: object
  Required:
    - subscriptionId
    - errorMessage
    - errorType
    - event
  Properties:
    subscriptionId:
      type: string
    errorMessage:
      type: string
    errorType:
      $ref: "#/components/schemas/ErrorType"
    errorEvent:
      oneOf:
        - $ref: "#/components/schemas/ErrorEvent"
      Discriminator:
        propertyName: errorTypeEvent
        Mapping:
          - invalidMessageError: "#/components/schemas/InvalidMessageErrorEvent"
          - projectPermissionErrorEvent: "#/components/schemas/ProjectPermissionErrorEvent"
          - missingFieldEvent: "#/components/schemas/MissingFieldErrorEvent"
          - invalidFieldValue: "#/components/schemas/InvalidFieldErrorEvent"
          - resourceError: "#/components/schemas/JobLogFileErrorEvent"
Components:
  Schemas:
    ErrorType:
      type: string
      Enum:
        - InvalidMessageError
        - ProjectPermissionError
        - MissingFieldError
        - InvalidFieldValueError
        - JobLogFileError
    GeneralErrorEvent:
      type: object
      Properties:
        exceptionMessage:
          description: A short error message description
          type: string
    ProjectPermissionErrorEvent:
      type: object
      Properties:
        projectId:
          description: A encoded base64 projectId
          type: string
    MissingFieldErrorEvent:
      type: object
      Properties:
        fieldName:
          description: The field name
          type: string
    InvalidFieldErrorEvent:
      type: object
      Properties:
        fieldName:
          type: string
        fieldValue:
          type: string
    JobLogFileErrorEvent:
      type: object
      Properties:
        details:
          description: the exception error message details
          type: string

Como exemplo, temos a seguinte mensagem de erro:

{
  "subscriptionId": "subId1",
  "errorMessage": "Error when monitoring job log file",
  "errorType": "JobLogFileError",
  "errorEvent": {
    "details": "File /projects/admin/test/.cmds/admi_test_CBOI65RCUO/logs/out.log does not exists.",
  }
}