commit a7c436fe4c7dd20f7da050559dbd69ca3631aff1 Author: Pedro Pérez Date: Wed Oct 8 23:17:23 2025 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5292519 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +logs/ \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8765f88 --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +GO ?= go +PG_VERSION := 17.6-alpine3.22 +MOD_NAME := nats-app +DB_NAME := nats-db +NATS_NAME := nats-sv +NATS_VERSION := 2.12.0-alpine3.22 + +.PHONY: dockerize-db +# Remove and create a development database. +dockerize-db: + docker rm -f $(DB_NAME) + docker run --name $(DB_NAME) -e POSTGRES_PASSWORD=secret -e POSTGRES_USER=developer -e POSTGRES_DB=$(DB_NAME) -p 5432:5432 -d postgres:$(PG_VERSION) + +.PHONY: dockerize-nats +# Remove and create a NATS server. +dockerize-nats: + docker rm -f $(NATS_NAME) + docker run --name $(NATS_NAME) -p 4222:4222 -d nats:$(NATS_VERSION) + +.PHONY: run +# Start app in development environment +run: + go run ./app/. + +.PHONY: run-prod +run-prod: +# Start app in production environment + go run ./app/. -env=prod \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..360f79e --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# NATS APP + +Lectura de datos de sensores en un dispositivo IoT. Prueba técnica para optar +por el puesto de programador Go. + +## Requisitos previos + +- Docker +- NATS CLI +- Make, si prefieres la comodidad de usar Makefile + +## Consideraciones + +Hay partes de códigos que son _snippets_ extraídos de una librería de autoría +propia. [Repositorio GitHub](https://github.com/zepyrshut/gopher-toolbox). De +las cuales son: + +- El _logger_ usando la _stdlib log/slog_. + +## Bitácora + +### Quickstart y toma de contacto con NATS + +Lo primero que he hecho es un _quickstart_ del proyecto, con lo que siempre o +casi siempre pongo en mis experimentos. Y lo siguiente, en lugar de empezar a +construir el proyecto como loco he tratado de entender cómo funciona NATS. Al +final ha sido muy sencillo, siguiendo esos pasos: + +1. Levantar el servidor NATS en Docker +2. Instalar el CLI de NATS +2. Abrir un puñado de terminales, y en un par de ellas escribir: `nats sub "hello"` +lo cual significa que se está suscribiendo al canal `hello`. Y en otra escribir: +`nats pub "hello" "Hola mundo!"`, lo cual significa que está escribiendo el +mensaje `Hola mundo!` en el canal `hello`. + +![demo de NATS](./assets/nats-demo-1.png) \ No newline at end of file diff --git a/app/main.go b/app/main.go new file mode 100644 index 0000000..bd2b9b5 --- /dev/null +++ b/app/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "flag" + "log/slog" + "nats-app/internal/app" +) + +func main() { + environment := flag.String("env", "dev", "dev or prod") + flag.Parse() + + _ = app.NewApp(*environment) + + slog.Debug("hello world debug") + slog.Info("Hello world info") + slog.Warn("Hello world warn") + slog.Error("hello world error") +} diff --git a/assets/nats-demo-1.png b/assets/nats-demo-1.png new file mode 100644 index 0000000..56d72ea Binary files /dev/null and b/assets/nats-demo-1.png differ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..fe4a28d --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module nats-app + +go 1.25.1 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/internal/app/app.go b/internal/app/app.go new file mode 100644 index 0000000..7ca991b --- /dev/null +++ b/internal/app/app.go @@ -0,0 +1,11 @@ +package app + +type App struct { +} + +func NewApp(environment string) *App { + + startRotativeLogger(environment) + + return &App{} +} diff --git a/internal/app/logger.go b/internal/app/logger.go new file mode 100644 index 0000000..de4def4 --- /dev/null +++ b/internal/app/logger.go @@ -0,0 +1,67 @@ +package app + +import ( + "fmt" + "io" + "log/slog" + "os" + "strings" + "time" +) + +var ( + logFile *os.File +) + +func newLogger(environment string) { + if err := os.MkdirAll("logs", 0755); err != nil { + fmt.Println("error creating logs directory:", err) + return + } + + now := time.Now().Format("2006-01-02") + f, err := os.OpenFile(fmt.Sprintf("logs/log%s.log", now), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + fmt.Println("error opening log file:", err) + return + } + + var level slog.Level + var addSource bool + + switch strings.ToLower(environment) { + case "dev": + level = slog.LevelDebug // -4 + addSource = true + case "prod": + level = slog.LevelInfo // 0 + addSource = false + default: + level = slog.LevelInfo + addSource = false + } + + mw := io.MultiWriter(os.Stdout, f) + logger := slog.New(slog.NewTextHandler(mw, &slog.HandlerOptions{ + AddSource: addSource, + Level: level, + })) + + if logFile != nil { + logFile.Close() + } + + logFile = f + slog.SetDefault(logger) +} + +func startRotativeLogger(environment string) { + newLogger(environment) + + ticker := time.NewTicker(time.Hour * 24) + go func() { + for range ticker.C { + newLogger(environment) + } + }() +}