Observability-mtl-instrument
O observability-mtl-instrument é um pacote que simplifica a instrumentação e configuração para coleta e envio de métricas, traces e logs. Por padrão a Stack utilizada é:
-
Métricas: Prometheus e PushGateway
-
Traces: Grafana/Tempo
-
Logs: Grafana/Loki
Pacote de observabilidade
Para simplificar ainda mais o gerenciamento, armazenamento e visualizações de métricas, traces e logs, além de integração com a biblioteca é possível utilizar o pacote de observabilidade, que traz um docker-compose, diversas configurações e exemplos de uso para containers de Prometheus, Grafana/Loki, Grafana/Tempo, Grafana e NGINX. Está disponível em: https://github.com/SergioRicJr/observability-package
Instalação
Instale e atualize usando pip:
pip install observability-mtl-instrument
Como usar
Métricas
importação da configuração de métricas:
from observability_mtl_instrument.metrics.metric_config import MetricConfig
Configuração básica para uso:
metric_config = MetricConfig(
job_name='nome escolhido para a aplicação',
prometheus_url='url do pushGateway para envio de métrica'
)
Chamada das métricas
As métricas utilizadas são baseadas e utilizam por baixo dos panos o prometheus_client, sendo assim, sejam as métricas padrões ou aquelas criadas por quem está usando, possuem os métodos e as formas de registrar as métricas seguindo a seguinte documentação: https://prometheus.github.io/client_python/. Sendo utilizado em código da seguinte forma:
metrics = metric_config.metrics
# As métricas podem ser utilizadas em um middleware da aplicação para funcionar de forma a poluir menos o código
metrics['requests_in_progress'].labels(service='fastapi-app').inc()
Envio das métricas
O envio das métricas registradas em código é realizado da seguinte forma:
metric_config.send_metrics()
Traces
importação da configuração dos traces:
from observability_mtl_instrument.tracer.trace_config import TraceConfig
Configuração básica para uso:
trace_config = TraceConfig(service_name="nome do serviço", tempo_url="http://endereço do tempo/tempo/v1/traces")
Criação de traces no código
Existem duas maneiras de adicionar trace no código, uma delas é com instrumentação automática, que gera os traces a cada chamada de api ou requisição feito durante uma chamada a um endpoint, um exemplo pode ser visto utilizando FastAPIInstrumentor:
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
trace = trace_config.get_trace()
FastAPIInstrumentor.instrument_app(app, tracer_provider=trace.get_tracer_provider())
A outra forma é realizando a criação manual dos traces:
tracer = trace_config.get_tracer()
with tracer.start_as_current_span("name"):
# Código que fará parte desse trace
É possível também adicionar eventos e atributos no centexto do tracer, para conhecer mais acesse https://opentelemetry-python.readthedocs.io/en/latest/api/trace.html
Logs
importação da configuração dos logs:
from observability_mtl_instrument.logs.builders.fullLogConfig import FullLogConfig
Configuração básica para uso:
log_config = FullLogConfig(
service_name='nome escolhido para a aplicação',
loki_url='http://<url-do-loki>/loki/api/v1/push'
).get_log_config()
obs: A configuração do log_level é feita importando a biblioteca logging e utilizando seus níveis de log, que são:
-
logging.DEBUG
-
logging.INFO
-
logging.WARN
-
logging.ERROR
-
logging.CRITICAL
por padrão o nível de log é DEBUG.
Chamada de logs
Os logs são configurados utilizando a biblioteca logging do python, sendo assim, para realizar a chamada e registro dos logs é necessário resgatar o logger em uma variável, como é possível ver na documentação do logging. Um exemplo de chamada é:
logger = log_config.get_logger()
# Essa linha de código é responsável por registrar um log do tipo e realizar seu envio ao Loki
logger.info('hello message was sent')
Informações adicionais
Métricas:
Tipos de métrica
O prometheus possui diversos tipos de métrica, que podem ser conhecidas através de sua documentação, em https://prometheus.io/docs/concepts/metric_types. O projeto observability-mtl-instrument, trabalha com todas elas.
Métricas default
É possível adicionar e criar métricas de acordo com o seu objetivo, porém, a biblioteca já apresenta três métricas por padrão, são elas:
http_requests_total_by_code:
Tipo: Counter
Labels:
| Nome | Tipo | Descrição |
|---|---|---|
http_code |
string |
Código do status HTTP. |
unmapped |
boolean |
True ou False, para dizer se a rota é ou não conhecida pela aplicação. |
service_name |
string |
Nome do serviço, aplicação ou job. |
http_requests_duration_seconds
Tipo: Summary
Labels:
| Nome | Tipo | Descrição |
|---|---|---|
url_path |
string |
Rota da requisição |
http_method |
string |
Método HTTP usado na requisição |
unmapped |
boolean |
True ou False, para dizer se a rota é ou não conhecida pela aplicação. |
service_name |
string |
Nome do serviço, aplicação ou job. |
requests_in_progress
Tipo: Gauge
Labels:
| Nome | Tipo | Descrição |
|---|---|---|
service_name |
string |
Nome do serviço, aplicação ou job. |
Adição de métricas
Além das métricas já existentes ao realizar a configuração, é possível criar outras completamente personalizadas, adicionando o título, seu tipo, sua descrição e os labels. Segue um exemplo dessa criação de métricas:
from observability_mtl_instrument.metric_config import MetricType
# Após instânciar MetricConfig
metric_config.add_metrics(
title="requests_in_progress",
type=MetricType.GAUGE,
description: "",
labels=['service']
)
Logs
Envio assíncrono de logs
É possível enviar os logs ao container Loki também de forma assíncrona, utilizando a classe FullLogConfigAsync ao invés da FullLogConfig, o que é feito da segunda forma:
log_config = FullLogConfigAsync(
service_name='nome escolhido para a aplicação',
loki_url='http://<url-do-loki>/loki/api/v1/push'
).get_log_config()
Vale ressaltar que o envio assíncrono de logs é suportado em funções assíncronas, como usadas no fastapi, ou flask, da seguinte forma:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def welcome():
logger.info('hello message was sent')
return {"message": "Hello, welcome to the application!"}
Obs: Ainda está sendo estudada a compatibilidade do envio assíncrono com aplicações utilizando o Framework Django.
Integração com Trace
A configuração padrão dos Logs já realiza a integração com os dados do Trace mas caso seja interessante para o uso escolhido, é possível alterar a formatação do log, alterando o parâmetro log_format ao instânciar o LogConfig:
log_config = FullLogConfig(
service_name='nome escolhido para a aplicação',
loki_url='http://<url-do-loki>/loki/api/v1/push',
log_format='%(asctime)s levelname=%(levelname)s name=%(name)s file=%(filename)s:%(lineno)d trace_id=%(otelTraceID)s span_id=%(otelSpanID)s resource.service.name=%(otelServiceName)s trace_sampled=%(otelTraceSampled)s - message="%(message)s"',
).get_log_config()
Labels
Os labels são utilizados para facilitar a busca por logs no Grafana/Loki, sendo assim, há duas formas possíveis de realizar a adição de labels:
- Na configuração dos logs, onde todos os logs apresentarão esses labels:
log_config = FullLogConfig(
service_name='nome escolhido para a aplicação',
loki_url='http://<url-do-loki>/loki/api/v1/push',
extra_labels={
"label1": "valor1",
"label2": "valor2"
}
).get_log_config()
- Em chamadas específicas de log:
logger.info('hello message was sent', extra={'extra_labels': {
"label3": "valor3"
}})