Skip to main content

Desvendando os Segredos do Sistema: A Magia da Observabilidade

· 16 min read

O objetivo deste artigo é instruir sobre os conceitos de Observabilidade, Logs, Métricas e Tracing, baseando-se na literatura e na experiência prática em grandes empresas.

Vamos aprender a aplicá-los em um projeto prático e entender como eles se relacionam.

“You can't manage what you can't measure.”

-- Peter Drucker


🔭 Afinal, Observabilidade

O termo surgiu em 1960 na área de engenharia de controle, introduzido pelo engenheiro Rudolf E. Kálmán, e foi popularizado por On The General Theory of Control Systems.

Na Teoria de Controle, Observabilidade é uma medida dos estados internos de um sistema que podem ser inferidos a partir do conhecimento de suas saídas externas.

O que significa que a observabilidade propōe saber como o sistema chegou em um estado olhando suas saídas.

Dessa forma permite lidar com problemas atuais e até solucionar problemas novos.

Mas nem tudo é mágica... Para ser capaz de identificar o estado de um sistema olhando suas saídas, a aplicação deve ser devidamente instrumentada, ou seja, deve emitir sinais que possam ser capturados e analisados.

Dividimos esses sinais em três pilares: traces, métricas e logs. Porém, só ter os sinais não é suficiente; é necessário coletar, transmitir e armazenar esses dados.

Chamamos a coleta, transmissão e armazenamento desses dados de Telemetria.

telemetria Generated by DALL-E

Uma aplicação é devidamente instrumentada quando os desenvolvedores não precisam adicionar mais instrumentos para solucionar um problema, porque eles têm todas as informações de que precisam, ou seja, sem a necessidade de fazer um novo deploy para adicionar mais instrumentos. Entenda como instrumentos: softwares novos, alterações de arquivos ou configurações, etc.

Claro, essa maturidade é atingida com o tempo, e é um processo contínuo de melhoria, seria impossível instrumentar tudo de uma vez, pois sempre haverá problemas que não se manifestaram... ainda.

Três Pilares da Observabilidade

PilarDescrição
MétricasMede quantitivamente, comumente usada para comparar, rastrear desempenho em uma linha do tempo.
LogsRegistro de que um evento ocorreu
TracingTracing é um mecanismo para acompanhar o fluxo e a progressão dos dados de um software

Conclusão

Aplicações bem instrumentadas são capazes de responder perguntas sobre seu comportamento análisando-as de fora, e isso é o que chamamos de Observabilidade.

Instrumentar aplicações é o ato de emitir sinais, esses sinais são divididos em 3 pilares: traces, métricas e logs.

Telemetria é o processo de coletar, transmitir e armazenar esses sinais.


1. 📜 Logs: Segredos Escondidos no Código

1.1. Introdução

Todo software executa métodos/funções, essas execuções na maioria das vezes tem algo as nos dizer que fazem valer a pena serem registradas. Então...
Logs são registro de eventos que ocorrem no software.

Abaixo listo alguns tipos de logs:

  • Application Logs
  • Audit Logs
  • Authorization and Access Logs
  • Change Logs
  • Event Logs
  • Resource Logs
  • Server Logs
  • System Logs
  • Threat Logs
  • Transaction Logs

Vamos abordar apenas os logs de aplicação, que são logs gerados por uma aplicação em execução.

Normalmente em um registro de log de aplicação encontramos:

  • Timestamp
  • Mensagem
  • Nível
  • Dados de Contexto

1.2. Níveis de Logs

  • DEBUG - Informações detalhadas, normalmente de interesse apenas para desenvolvedores.
  • INFO - Captura eventos que ocorreram, mas que não são erros.
  • WARN - Indica que algo inesperado aconteceu, mas que o software ainda é capaz de funcionar.
  • ERROR - Indica que pelo menos um componente do sistema falhou e pode interferir em parte ou no funcionamento geral.
  • FATAL - Indica que o sistema não pode continuar a funcionar.

1.3. Formato de Logs

Abaixo vamos ver algumas estruturas de logs mais encontradas no mercado. O JSON costuma ser o mais utilizado por ser fácil de parsear e por ser estruturado.

1.3.1. Estruturados

structured_logs

No exemplo acima perceba que existem dois campos trace_id e span_id, vamos aborda-los mais a frente.


1.3.2. Semi-Estruturados

CEF:0|Security|ThreatDetection|1.0|100|User logged in|5|msg=User logged in src=127.0.0.1 user=john.doe

1.3.3. Desestruturados

unstructured_logs


Qual a diferença entre Semi-Estruturado, Estruturado e Desestruturado?

Semi-Estruturados possuem uma estrutura fixa, porém os campos não são fixos.

Por exemplo, Logfmt tem sempre a mesma estrutura key=value com separador de espaço, porém os campos podem variar.

Estruturados possuem uma estrutura fixa e os campos são fixos.

Por exemplo, JSON possui sempre os mesmos campos (se assim for implementado), ou seja, todos os logs possuem os mesmos campos, como timestamp, level, message e extra, porém os valores podem variar.

Desestruturados não possuem uma estrutura fixa, ou seja, os campos e valores podem variar.

Por exemplo, logs de texto puro.

1.4. Implementação

Como utilizar logs em uma aplicação? Abaixo vemos um snippet de código da aplicação Flask em Python que construi para exemplificar esse artigo.

@app.route('/')
def hello():
message = f"Hello from {MS_NAME}!"
Logger().info('app.py', {"response": message, "status": "200", "method": "GET", "path": "/", "app": MS_NAME})

return message

O código está emitindo um log estruturado através da abstração Logger para sobrescrever o comportamento padrão do logging do Python.

Agora vamos análisar o output em JSON do log gerado por esse código.

{
"timestamp": "2024-07-05 19:59:37",
"level": "INFO",
"message": "app.py",
"extra": {
"response": "Hello from ms-one!",
"status": "200",
"method": "GET",
"path": "/",
"app": "ms-one"
}
}

O destino desse output pode ser um arquivo, um banco de dados, um serviço de log, etc. No nosso caso estamos apenas imprimindo o log no console stdout e stderr.

1.5. Arquitetura

Logs

info

No exemplo acima o promtail é um agente que coleta logs dos containers e envia para o Loki, que é um sistema de armazenamento de logs enquanto o Grafana é uma ferramenta de visualização de logs que consome diretamente do Loki.

Armazenar logs acaba se tornando caro 💰 ao longo do tempo, por isso é importante definir os eventos que realmente possuem valor e padronizar a estrutura de output.

Busque pontos estratégicos dentro do código da aplicação para emitir logs com contextos relevantes, isso vai ajudar a trackear o comportamento do software do lado de fora usando alguma ferramenta de visualização de logs e permitir que com esses dados sejam montadas dashboards com métricas que irão facilitar a descoberta de problemas e auxiliarão em tomadas de decisões.

Conclusão

O log estruturado permite que você faça consultas mais complexas de forma mais fácil, e consiga através dos logs construir métricas.

No log desestruturado o parseamento é mais difícil pois a estrutura não é fixa, e a construção de métricas pode se tornar mais complexa. Pode ocasionar na quebra de dashboards e métricas dependendo de como a consulta foi construída.

warning

Não que não seja possível fazer métricas com os logs Semi-Estruturado e Desestruturado, mas acaba se tornando difícil quando não se possui um padrão.

tip

Defina as boas práticas de logging para sua aplicação e siga-as:

  • Defina os níveis de logs que devem ser emitidos em produção, não emita debug.
  • Defina o formato de output (JSON, XML, CSV...).
    • Defina os campos que devem ser emitidos.
    • Defina quando usar log de erro.
    • Defina quando usar log de info.
    • Defina quando usar log de warning.
    • Defina quando usar log de fatal.
  • Projete o código para emitir logs em pontos estratégicos.
  • Defina o destino dos logs (stdout, stderr, arquivo, banco de dados, serviço de log, etc)
  • Defina a politica de retenção de logs (X dias).

2. 📈 Métricas: Decifrando o Desempenho do Software

Métricas são valores capturados em seus sistemas em um ponto específico no tempo.

Exemplos:

  • o número de usuários que logaram em seus sistema nas últimas 2h
  • o número de requisições que sua aplicação recebeu nas últimas 24h
  • o tempo médio de resposta de uma requisição dentro das últimas 1h

Podem ser coletadas uma vez por segundo, uma por minuto ou em outro intervalo regular para monitorar um sistema ao longo do tempo. Seus dados são comumente armazenados em um banco de dados de séries temporais, como o InfluxDB, Prometheus, Graphite, etc. No Prometheus a coleta é feita através de scraping.

Também construimos métricas através dos logs de aplicação, filtrando e agregando os dados dentro de um intervalo de tempo. Chamamos essa técnica de White-box monitoring.

Por Exemplo, a partir dos logs da aplicação que possuem o campo extra_path="/health", podemos contar quantas vezes a rota /health foi requisitada em um intervalo de tempo. Veja abaixo:

Metrics

Foram feitos 3 requests para a rota /health em um intervalo de ~1 minutos. Neste caso construimos uma métrica de Tráfego (Traffic).

2.1. Métricas de Recursos (CPU, Memória, Disco, Rede)

Métricas de recursos são métricas que medem o uso de recursos de um sistema, como CPU, memória, disco e rede. Essas métricas são essenciais para monitorar a saúde e o desempenho de um sistema e identificar gargalos e problemas de desempenho.

SinalDescrição
UtilizationMede a quantidade de recurso que está sendo usada.
SaturationMede o grau em que um recurso está perto de sua capacidade máxima ou além.
ErrorsMede a quantidade de erros que ocorreram ao usar um recurso.
JitterMede a variação que o pacote na rede é transmitido e quando ele é recebido.
CapacityMede a quantidade máxima de trabalho que um sistema pode realizar. Ex: 8GB RAM, 3vCPU

Metrics

2.2. Métricas de Serviço (Four Golden Signals)

Apresentadas no livro SRE do Google, essas 4 métricas são destinadas a entender a experiência de uso do sistema do ponto de vista dos usuários - Service-Oriented.

SinalDescrição
LatencyMede o tempo que o sistema leva para responder a uma solicitação.
TrafficMede o número de requisições que o sistema está recebendo.
ErrorsMede o número de erros que o sistema está retornando.
SaturationMede o grau no qual um recurso está perto da sua capacidade máxima ou além.

A métrica de desempenho mais comum é a latência, que representa o tempo necessário para concluir uma unidade de trabalho. A latência pode ser expressa como uma média ou como um percentil, como "99% das solicitações retornaram em 0,1s".

tip

A média é muito usada para entender o desempenho geral de um sistema, porém a média não é um valor tão confiável, pois pode ser distorcida por valores extremos, já os percentis são úteis para entender o desempenho em diferentes quantis da distribuição.

Abaixo mostro uma dashboard para monitorar o uso total de CPU de um Container. Fiz uma simulação de 1000 requests e podemos ver o pico de CPU.

Metrics

Metrics

2.3. Algumas Boas Práticas

  • Fácil de Entender Deve ser possivel determinar rapidamente o que cada métrica ou evento representa.

  • Custo Armazenar métricas pode ser caro, então você deve coletar apenas o que é realmente necessário.

  • Granular Se você coletar métricas muito agregadas, você pode perder informações importantes. Por exemplo, se você coletar a média de latência de uma solicitação, você pode perder informações sobre picos de latência que podem ser importantes para a experiência do usuário. Se você coletar métricas muito granulares, você pode acabar com muitos dados que são difíceis de analisar. Encontre um equilíbrio entre a granularidade e a agregação que funcione para você.

  • Etiqueta de Escopo Cada um dos seus hosts opera simultaneamente em vários escopos, e você pode querer verificar a saúde de qualquer um desses escopos, ou suas combinações. Por exemplo: como está a métrica X total? E a métrica X no Nordeste dos EUA?.

  • Vida útil Mantenha uma política de retenção de dados que permita que você investigue problemas passados e identifique tendências ao longo do tempo.

2.4. Arquitetura

Metrics

Conclusão

As métricas conseguem informar o estado passado e presente do sistema, e ajudam a prever comportamentos que podem ocorrer no futuro.

Respondem qual parte do software está sendo mais acessada, qual parte está consumindo mais recursos, qual parte está mais lenta...

Não se resumem a apenas métricas de recurso, mas também a métricas de negócio, como o número de vendas, o número de usuários ativos...

Mas lembre-se, métricas são apenas números, e para entender o que esses números significam, você precisa de logs.


3. 👣 Tracing: Rastreando Segredos

O objetivo principal do tracing é fornecer visibilidade detalhada sobre como o processamento daquela chamada foi feito, e como ela se relaciona com outras chamadas.

Quando essa requisição se extende para fora da aplicação, o tracing se torna distribuído, chamamos-o de Distributed Tracing.

Uma aplicação CLI por exemplo, pode ser instrumentada para gerar traces.

Isso é feito registrando cada operação ou evento importante durante o ciclo de vida de uma requisição.

Um dos principais componentes do tracing é o conceito de span.

TRACE
|-- SPAN
| |-- LOG
| |-- LOG
| |-- LOG
|-- SPAN
| |-- LOG
| |-- LOG

Note que um span pode conter vários logs.

Um span representa uma unidade de trabalho ou operação e contém informações como:

  • Nome da operação
  • Início e fim da operação (timestamps)
  • Metadados ou atributos adicionais (como IDs de usuário, tipos de operações, etc.)
  • Relacionamento com outros spans (pai-filho)
  • Logs associados à operação

Os spans podem ser organizados em uma hierarquia que reflete a estrutura da execução da requisição, facilitando a visualização de como uma requisição é processada de ponta a ponta dentro de uma aplicação.

Exemplo

Abaixo mostro o output de uma classe python que criei que gera logs estruturados. Nela é possível ver dois spans que foram gerados para o mesmo trace_id.

Porém ela é básica, ou seja, não possui o conceito de que um span possui 1..N logs. Vemos que 1 span possui 1 log.

Perceba que o trace_id é o mesmo para os dois spans, isso indica que ambos os spans fazem parte da mesma requisição.

Essa requisição está executando 2 métodos diferentes da aplicação, e ambos os métodos estão gerando logs.

tracing

3.1. Distributed Tracing

Tracing

O distributed tracing (rastreamento distribuído) se expande para ambientes onde múltiplas aplicações e serviços colaboram para processar uma requisição.

Em um sistema distribuído, uma única requisição pode atravessar várias aplicações e serviços, cada um deles contribuindo com uma parte do processamento.

O distributed tracing tem como objetivo fornecer visibilidade ponta a ponta de como as requisições fluem através de um sistema distribuído, identificando pontos de latência, gargalos e falhas.

distributed-tracing

Conclusão

O tracing é uma ferramenta poderosa para entender como as requisições são processadas em uma aplicação e como elas se relacionam com outras requisições.

O tracing é especialmente útil em sistemas distribuídos, onde várias aplicações e serviços colaboram para processar uma requisição.

Mas também é muito útil em sistemas onde uma requisição pode ser processada por várias partes da aplicação.


4. 🚨 Monitoramento: Desvendando Mistérios em Tempo Real

Monitoramento é o ato de ativamente observar o estado de um sistema para detectar problemas e tomar medidas corretivas.

O monitoramento é uma parte essencial da observabilidade, pois fornece informações em tempo real sobre o estado de um sistema e ajudam a identificar que está ocorrendo problemas.

Usamos alertas para notificar os operadores do sistema quando alguma politica de alerta é violada. Esses alertas ajudam a manter os operadores do sistema cientes sobre o que está acontecendo.

Exitem duas abordagens de Monitoramento, chamamos de Ativo e Passivo

AspectoMonitoramento AtivoMonitoramento Passivo
MétodoEnvia requisições/sondas ao sistemaObserva o tráfego de rede e coleta logs
ProatividadeProativoReativo
SobrecargaPode adicionar carga adicional ao sistemaBaixa sobrecarga
Detecção de ProblemasDetecta problemas rapidamente através de sondagens constantesPode detectar problemas após eles terem ocorrido
VantagensDetecção rápida, ação preventiva, métricas detalhadasBaixa sobrecarga, informações detalhadas, análise histórica
DesvantagensSobrecarga de rede, possíveis falsos positivosReatividade, dependência de qualidade de logs

A prática do monitoramento ativo é comum em sistemas críticos, onde a detecção de problemas e ação preventiva são essenciais para garantir a disponibilidade e confiabilidade do sistema.

Pense que você fica cutucando o sistema para ver se ele está responsivo e funcionando como deveria.

Já a prática do monitoramento passivo é comum para todos os sistemas.

Conclusão

Para monitorar um sistema de forma eficaz, defina métricas e thresholds para que quando um threshold for violado, um alerta seja disparado e o operador do sistema receba notificação.

Mas tome cuidado com o excesso de alertas, pois isso pode levar a fadiga de alertas e fazer com que os operadores ignorem alertas importantes.


Referências

Understand Shell Script

· 7 min read

Summary:

Repository on GitHub


Goal:


My intention is to give to you an idea of how you can create your own shell script commands to automate some tasks and show to you some references (https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html) (https://www.shellscript.sh).

Shell script has Variables, Repetition Structures, Conditional Statements and commands UNIX so you can create scripts to use as entrypoint.sh on docker container or just to automate one boring manual task in your S.O.

e.g: This blog is deployed with this Shell Script that generate a build, commit, push to gh-pages branch in my repo.

You can use CLI commands from others softwares installed in your machine, like GIT, Docker, node, npm...

If you understand the power of shell script your imagination will be the limit.

Without further ado...

1. Introduction


What is Shell Script?

  • A shell script is a text file that contains a sequence of commands for a UNIX-based operating system. It is called a shell script because it combines a sequence of commands, that would otherwise have to be typed into the keyboard one at a time, into a single script.

A Unix shell is both a command interface and a programming language

In our case the computer program is example.sh file


1.1 Create a file called example.sh using terminal, follow:

touch example.sh
chmod +x example.sh
  • touch: will create the file
  • chmod +x: will give permission to execute file script

You can use touch and chmod commands inside your shell script file too


1.2 Open file example.sh in your preferred IDE

#!/bin/sh

echo "Start" # this command will output in terminal Start string
  • #!/bin/sh tells Unix that the file is to be executed by /bin/sh.

1.3 Running file example.sh in terminal

sh example.sh

1.4 Create a new file using shell script

#!/bin/sh
echo "Start"
touch new_file.txt # create new file called new_file.txt
echo "First Line">> new_file.txt # append in new_file.txt the text First Line

2. Variables


A variable is a character string to which we assign a value. The value assigned could be a number, text, filename, device, or any other type of data.

2.1 Declare Variable

#!/bin/sh

VARIABLE_MY_NAME="Vinicius"
VARIABLE_TEXT="Hi, my name is "
echo "$VARIABLE_TEXT $VARIABLE_MY_NAME"

Terminal Output:

Hi, my name is Vinicius

2.2 Read Input Variable

#!/bin/sh

echo "What is your name?"
read VARIABLE_MY_NAME

VARIABLE_TEXT="Hi,"

echo "$VARIABLE_TEXT $VARIABLE_MY_NAME"

Terminal Output:

What is your name?
> Vinicius

Hi, Vinicius

3. Loops


Loops will enable you to execute one or more commands repeatedly

3.1 For

#!/bin/sh
for i in 1 2 3 4 5
do
echo "Looping ... number $i"
done

3.2 While

#!/bin/sh
while :
do
echo "Please type something in (^C to quit)"
read INPUT_STRING
echo "You typed: $INPUT_STRING"
done

4. Comparison


Sometimes you need to check if one variable is equal or not to another or greater/less...
In this section you can check ways to compare numbers and strings variables.

4.1 Numbers Comparison

# is equal to:
if [ "$a" -eq "$b" ]

# is not equal to:
if [ "$a" -ne "$b" ]

# is greater than:
if [ "$a" -gt "$b" ]

# is greater than or equal to:
if [ "$a" -ge "$b" ]

# is less than: -lt
if [ "$a" -lt "$b" ]

# is less than or equal to:
if [ "$a" -le "$b" ]

# is less than (within double parentheses):
(("$a" < "$b"))

# is less than or equal to (within double parentheses):
(("$a" <= "$b"))

# is greater than (within double parentheses):
(("$a" > "$b"))

# is greater than or equal to (within double parentheses):
(("$a" >= "$b"))

4.2 String Comparison

# is equal to: 
if [ "$a" = "$b" ]

# is equal to:
if [ "$a" == "$b" ]

# is not equal to:
if [ "$a" != "$b" ]

# is less than, in ASCII alphabetical order:
if [[ "$a" < "$b" ]]

# is greater than, in ASCII alphabetical order:
if [[ "$a" > "$b" ]]

# string is null, that is, has zero length:
if [ -z "$String" ]
then
echo "\$String is null."
else
echo "\$String is NOT null."
fi

# string is not null:
if [ -n "$String" ]
then
echo "\$String is not null."
else
echo "\$String is null."
fi

5. Final example.sh


Copy the script below and run it on your machine and after use, edit to your needs

Repository on GitHub

#!bin/sh

script() {
echo "Start" # Output Start in terminal

# Receive variable by user input
echo "Put your name" # Output Put your name in terminal
read name # Wait/Receive input variable name by user
echo Your name is ${name} # Output Your name is variable name

# Execute commands of another programs (like docker, git...)
docker -v # docker version
git --version # git version


rm Dockerfile # Remove old Dockerfile
touch Dockerfile # Create Dockerfile
echo "FROM alpine:3.14" >>Dockerfile # Input echo string inside file
echo 'ENTRYPOINT ["echo", "Container running 👍 ( ͡° ͜ʖ ͡°)"]' >>Dockerfile # Input echo string inside file
echo 'CMD ["'${name}'"] && /dev/null' >>Dockerfile # Input echo string inside file

# Build and Execute Docker Container
imageName="${name}-docker-image" # docker image name variable
docker build -t ${imageName} . # build image
docker images # list images

# Generate utils variables
timestamp=$(date +%s) # create variable timestamp to attach in container name
containerName="${name}-docker-container-${timestamp}" # variable container name to assign in run command

# Running command and see logs
echo "-------"
echo "Start: " && docker run -d --name ${containerName} ${imageName} # running container
echo "-------"
echo "Logs: " && docker logs ${containerName} # output logs container
echo "-------"
# Remove all Containers
containers=$(docker ps -a --format "{{.Names}}" | grep "${name}-docker-container") # list all containers created in this script
docker rm -f ${containers} # remove all containers created in this script
docker ps -a # list all containers

# Remove all Images
imgs=$(docker images --format "{{.Repository}}" | grep "${imageName}") # imgs created repository
docker rmi -f ${imgs} # Remove images in imgs variable
}

script | tee ex.log # fill log file called ex.log

Steps:

  1. output "start" in terminal
  2. output "Put your name" in terminal
  3. wait user input variable name
  4. output "Your name is ..." in terminal
  5. check docker version
  6. check git version
  7. remove old Dockerfile
  8. create new Dockerfile empty
  9. write in file Dockerfile echo string to create a valid docker image
  10. build image
  11. run container
  12. output logs container
  13. remove all containers created (normally only one)
  14. remove all images created (normally only one)

Use SOURCES to study and deepen your knowledge

Sources: