# Creación de la aplicación


## Requisitos previos

- Tienes un [cliente de Git](https://git-scm.com/downloads). Los ejemplos de esta sección utilizan un cliente de Git basado en la línea de comandos, pero puedes utilizar cualquier cliente.

Crearás un servidor Golang con algunos endpoints para simular una aplicación del mundo real. Luego, expondrás métricas desde el servidor usando Prometheus.

## Obtención de la aplicación de ejemplo

Clona la aplicación de ejemplo para usarla con esta guía. Abre una terminal, cambia al directorio en el que quieras trabajar y ejecuta el siguiente comando para clonar el repositorio:

```console
$ git clone https://github.com/dockersamples/go-prometheus-monitoring.git
```

Una vez clonado, verás la siguiente estructura de contenido dentro del directorio `go-prometheus-monitoring`:

```text
go-prometheus-monitoring
├── CONTRIBUTING.md
├── Docker
│   ├── grafana.yml
│   └── prometheus.yml
├── dashboard.json
├── Dockerfile
├── LICENSE
├── README.md
├── compose.yaml
├── go.mod
├── go.sum
└── main.go
```

- **main.go** - El punto de entrada de la aplicación.
- **go.mod y go.sum** - Archivos de módulos de Go.
- **Dockerfile** - El Dockerfile utilizado para construir la aplicación.
- **Docker/** - Contiene los archivos de configuración de Docker Compose para Grafana y Prometheus.
- **compose.yaml** - El archivo Compose para lanzar todo (la aplicación Golang, Prometheus y Grafana).
- **dashboard.json** - Archivo de configuración del tablero de Grafana.
- **Dockerfile** - El Dockerfile utilizado para construir la aplicación Golang.
- **compose.yaml** - El archivo Docker Compose para lanzar todo (la aplicación Golang, Prometheus y Grafana).
- Otros archivos son para fines de licencia y documentación.

## Entendiendo la aplicación

La siguiente es la lógica completa de la aplicación que encontrarás en `main.go`.

```go
package main

import (
	"strconv"

	"github.com/gin-gonic/gin"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

// Definir métricas
var (
	HttpRequestTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "api_http_request_total",
		Help: "Total number of requests processed by the API",
	}, []string{"path", "status"})

	HttpRequestErrorTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "api_http_request_error_total",
		Help: "Total number of errors returned by the API",
	}, []string{"path", "status"})
)

// Registro personalizado (sin las métricas predeterminadas de Go)
var customRegistry = prometheus.NewRegistry()

// Registrar métricas con el registro personalizado
func init() {
	customRegistry.MustRegister(HttpRequestTotal, HttpRequestErrorTotal)
}

func main() {
	router := gin.Default()

	// Registrar /metrics antes del middleware
	router.GET("/metrics", PrometheusHandler())

	router.Use(RequestMetricsMiddleware())
	router.GET("/health", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "Up and running!",
		})
	})
	router.GET("/v1/users", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "Hello from /v1/users",
		})
	})

	router.Run(":8000")
}

// Manejador de métricas personalizado con registro personalizado
func PrometheusHandler() gin.HandlerFunc {
	h := promhttp.HandlerFor(customRegistry, promhttp.HandlerOpts{})
	return func(c *gin.Context) {
		h.ServeHTTP(c.Writer, c.Request)
	}
}

// Middleware para registrar las métricas de las solicitudes entrantes
func RequestMetricsMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		path := c.Request.URL.Path
		c.Next()
		status := c.Writer.Status()
		if status < 400 {
			HttpRequestTotal.WithLabelValues(path, strconv.Itoa(status)).Inc()
		} else {
			HttpRequestErrorTotal.WithLabelValues(path, strconv.Itoa(status)).Inc()
		}
	}
}
```

En esta parte del código, has importado los paquetes requeridos `gin`, `prometheus` y `promhttp`. Luego has definido un par de variables, `HttpRequestTotal` y `HttpRequestErrorTotal` que son métricas de tipo contador de Prometheus, y `customRegistry` que es un registro personalizado que se utilizará para registrar estas métricas. El nombre de la métrica es una cadena que puedes usar para identificarla. La cadena de ayuda (help string) es un texto que se mostrará cuando realices consultas al endpoint `/metrics` para comprender la métrica. La razón por la que utilizas el registro personalizado es para evitar las métricas predeterminadas de Go que el cliente de Prometheus registra de forma predeterminada. Luego, mediante la función `init`, registras las métricas en el registro personalizado.

```go
import (
	"strconv"

	"github.com/gin-gonic/gin"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

// Definir métricas
var (
	HttpRequestTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "api_http_request_total",
		Help: "Total number of requests processed by the API",
	}, []string{"path", "status"})

	HttpRequestErrorTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "api_http_request_error_total",
		Help: "Total number of errors returned by the API",
	}, []string{"path", "status"})
)

// Registro personalizado (sin las métricas predeterminadas de Go)
var customRegistry = prometheus.NewRegistry()

// Registrar métricas con el registro personalizado
func init() {
	customRegistry.MustRegister(HttpRequestTotal, HttpRequestErrorTotal)
}
```

En la función `main`, has creado una nueva instancia del framework `gin` y has configurado tres rutas. Puedes ver el endpoint de salud en la ruta `/health` que devolverá un JSON con `{"message": "Up and running!"}` y el endpoint `/v1/users` que devolverá un JSON con `{"message": "Hello from /v1/users"}`. La tercera ruta es para el endpoint `/metrics` que devolverá las métricas en el formato de Prometheus. También tienes el middleware `RequestMetricsMiddleware`, el cual se ejecutará para cada solicitud realizada a la API. Este registrará las métricas de las solicitudes entrantes, como los códigos de estado y las rutas. Finalmente, ejecutas la aplicación gin en el puerto 8000.

```golang
func main() {
	router := gin.Default()

	// Registrar /metrics antes del middleware
	router.GET("/metrics", PrometheusHandler())

	router.Use(RequestMetricsMiddleware())
	router.GET("/health", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "Up and running!",
		})
	})
	router.GET("/v1/users", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "Hello from /v1/users",
		})
	})

	router.Run(":8000")
}
```

Ahora viene la función middleware `RequestMetricsMiddleware`. Esta función se ejecuta para cada solicitud realizada a la API. Incrementa el contador `HttpRequestTotal` (un contador diferente para cada ruta y código de estado) si el código de estado es menor que 400. Si el código de estado es mayor o igual a 400, incrementa el contador `HttpRequestErrorTotal` (un contador diferente para cada ruta y código de estado). La función `PrometheusHandler` es el manejador personalizado que se llamará para el endpoint `/metrics`. Devolverá las métricas en el formato de Prometheus.

```golang
// Manejador de métricas personalizado con registro personalizado
func PrometheusHandler() gin.HandlerFunc {
	h := promhttp.HandlerFor(customRegistry, promhttp.HandlerOpts{})
	return func(c *gin.Context) {
		h.ServeHTTP(c.Writer, c.Request)
	}
}

// Middleware para registrar las métricas de las solicitudes entrantes
func RequestMetricsMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		path := c.Request.URL.Path
		c.Next()
		status := c.Writer.Status()
		if status < 400 {
			HttpRequestTotal.WithLabelValues(path, strconv.Itoa(status)).Inc()
		} else {
			HttpRequestErrorTotal.WithLabelValues(path, strconv.Itoa(status)).Inc()
		}
	}
}
```

Eso es todo, esta es la esencia completa de la aplicación. Ahora es momento de ejecutarla y probar si la aplicación está registrando las métricas correctamente.

## Ejecución de la aplicación

Asegúrate de seguir dentro del directorio `go-prometheus-monitoring` en la terminal y ejecuta el siguiente comando. Instala las dependencias ejecutando `go mod tidy` y luego compila y ejecuta la aplicación con `go run main.go`. Después, visita `http://localhost:8000/health` o `http://localhost:8000/v1/users`. Deberías ver la salida `{"message": "Up and running!"}` o `{"message": "Hello from /v1/users"}`. Si puedes ver esto, tu aplicación está en funcionamiento con éxito.

Ahora, verifica las métricas de tu aplicación accediendo al endpoint `/metrics`. Abre `http://localhost:8000/metrics` en tu navegador. Deberías ver una salida similar a la siguiente.

```sh
# HELP api_http_request_error_total Total number of errors returned by the API
# TYPE api_http_request_error_total counter
api_http_request_error_total{path="/",status="404"} 1
api_http_request_error_total{path="//v1/users",status="404"} 1
api_http_request_error_total{path="/favicon.ico",status="404"} 1
# HELP api_http_request_total Total number of requests processed by the API
# TYPE api_http_request_total counter
api_http_request_total{path="/health",status="200"} 2
api_http_request_total{path="/v1/users",status="200"} 1
```

En la terminal, presiona `ctrl` + `c` para detener la aplicación.

> [!Note]
> Si no quieres ejecutar la aplicación localmente y prefieres ejecutarla en un contenedor Docker, salta a la siguiente página donde crearás un Dockerfile y contenedorizarás la aplicación.

## Resumen

En esta sección, aprendiste a crear una aplicación Golang para registrar métricas con Prometheus. Al implementar funciones middleware, pudiste incrementar los contadores según la ruta de la solicitud y los códigos de estado.

## Próximos pasos

En la siguiente sección, aprenderás a contenedorizar tu aplicación.

