feat: integrate RabbitMQ for logging and enhance logger with RabbitMQ hook
Deploy Application / deploy (push) Failing after 6s Details

This commit is contained in:
Habib Fatkhul Rohman 2025-12-02 15:00:19 +07:00
parent 5c27453315
commit 27c465342d
6 changed files with 205 additions and 7 deletions

View File

@ -103,8 +103,14 @@ func run(server *gin.Engine) {
// @externalDocs.url https://swagger.io/resources/open-api/
func main() {
logger := config.SetupLoggerLogrus()
var (
injector = do.New()
)
// Inisialisasi RabbitMQ
config.InitRabbitMQ()
logger := config.SetupLoggerLogrus()
// 🔹 Tangkap panic global di luar Gin (worker, cron, dsb.)
defer func() {
if r := recover(); r != nil {
@ -115,10 +121,6 @@ func main() {
}
}()
var (
injector = do.New()
)
providers.RegisterDependencies(injector)
// set Swagger info (opsional)

View File

@ -2,11 +2,14 @@ package config
import (
"encoding/json"
"fmt"
"log"
"os"
"reflect"
"strings"
"time"
amqp "github.com/rabbitmq/amqp091-go"
"github.com/sirupsen/logrus"
"gorm.io/gorm/logger"
)
@ -64,12 +67,21 @@ func SetupLoggerLogrus() *logrus.Logger {
logger := logrus.New()
logger.SetFormatter(&GroupedFieldsFormatter{TimestampFormat: time.RFC3339})
logger.SetOutput(logFile)
// ✔️ Pasang RabbitMQ Hook kalau channel berhasil connect
if RabbitChannel != nil {
queue := os.Getenv("RABBITMQ_LOG_QUEUE") // contoh: log_queue
hook := NewRabbitMQHook(RabbitChannel, queue)
logger.AddHook(hook)
}
logLevel := strings.ToLower(os.Getenv("LOG_LEVEL"))
if logLevel == "production" {
logger.SetLevel(logrus.WarnLevel)
} else {
logger.SetLevel(logrus.TraceLevel)
}
return logger
}
@ -86,14 +98,121 @@ func (f *GroupedFieldsFormatter) Format(entry *logrus.Entry) ([]byte, error) {
data["msg"] = entry.Message
data["time"] = entry.Time.Format(f.TimestampFormat)
serialized, err := json.Marshal(data)
serialized, err := json.Marshal(sanitizeJSON(data))
if err != nil {
return nil, err
}
return append(serialized, '\n'), nil
}
func sanitizeJSON(v interface{}) interface{} {
switch val := v.(type) {
// kalau error → convert ke string
case error:
return val.Error()
// kalau stringer → gunakan String()
case fmt.Stringer:
return val.String()
}
// kalau map → iterate dan sanitize value di dalamnya
if m, ok := v.(map[string]interface{}); ok {
safeMap := make(map[string]interface{})
for k, val := range m {
safeMap[k] = sanitizeJSON(val)
}
return safeMap
}
// func tidak boleh diserialize
if reflect.TypeOf(v).Kind() == reflect.Func {
return "<func>"
}
// struct kompleks → fallback string agar tidak error
_, err := json.Marshal(v)
if err != nil {
return fmt.Sprintf("%+v", v)
}
return v
}
// 🔹 Arahkan log bawaan Go ke logrus (biar library lain ikut nulis ke file)
func RedirectDefaultLogger(logger *logrus.Logger) {
log.SetOutput(logger.Writer())
}
type RabbitMQHook struct {
Channel *amqp.Channel
Queue string
}
func NewRabbitMQHook(ch *amqp.Channel, queue string) *RabbitMQHook {
return &RabbitMQHook{
Channel: ch,
Queue: queue,
}
}
func (h *RabbitMQHook) Levels() []logrus.Level {
return logrus.AllLevels
}
func (h *RabbitMQHook) Fire(entry *logrus.Entry) error {
msg := map[string]interface{}{
"level": entry.Level.String(),
"msg": entry.Message,
"time": entry.Time.Format(time.RFC3339),
"data": entry.Data,
}
body, err := json.Marshal(msg)
if err != nil {
return err
}
return h.Channel.Publish(
"",
h.Queue,
false,
false,
amqp.Publishing{
ContentType: "application/json",
Body: body,
},
)
}
// type LogMessage struct {
// Level string `json:"level"`
// Message string `json:"message"`
// Time time.Time `json:"time"`
// }
// func SendLog(level, message string) {
// if RabbitChannel == nil {
// return
// }
// logData := LogMessage{
// Level: level,
// Message: message,
// Time: time.Now(),
// }
// body, _ := json.Marshal(logData)
// RabbitChannel.Publish(
// "", // default exchange
// "app_logs", // queue name
// false,
// false,
// amqp.Publishing{
// ContentType: "application/json",
// Body: body,
// },
// )
// }

56
config/rabbitmq.go Normal file
View File

@ -0,0 +1,56 @@
package config
import (
"fmt"
"log"
"os"
amqp "github.com/rabbitmq/amqp091-go"
)
var RabbitConn *amqp.Connection
var RabbitChannel *amqp.Channel
func InitRabbitMQ() {
host := os.Getenv("RABBITMQ_HOST")
port := os.Getenv("RABBITMQ_PORT")
user := os.Getenv("RABBITMQ_USER")
pass := os.Getenv("RABBITMQ_PASSWORD")
queue := os.Getenv("RABBITMQ_LOG_QUEUE") // contoh: log_queue
uri := fmt.Sprintf("amqp://%s:%s@%s:%s/",
user, pass, host, port,
)
fmt.Println("Connecting to RabbitMQ:", uri)
conn, err := amqp.Dial(uri)
if err != nil {
log.Printf("RabbitMQ connect error: %v\n", err)
return
}
ch, err := conn.Channel()
if err != nil {
log.Printf("RabbitMQ channel error: %v\n", err)
return
}
_, err = ch.QueueDeclare(
queue,
true,
false,
false,
false,
nil,
)
if err != nil {
log.Printf("RabbitMQ declare queue error: %v\n", err)
return
}
RabbitConn = conn
RabbitChannel = ch
log.Println("RabbitMQ connected and queue declared:", queue)
}

View File

@ -8,6 +8,24 @@ services:
- .:/app
ports:
- ${GOLANG_PORT:-8888}:8888
env_file:
- .env # <--- WAJIB ADA
depends_on:
- rabbitmq
networks:
- default
rabbitmq:
image: rabbitmq:3.13-management-alpine
container_name: rabbitmq
ports:
- "${RABBITMQ_PORT:-5672}:5672" # port host 5672, port container 5672
- "${RABBITMQ_MANAGEMENT_PORT:-15672}:15672" # port untuk management UI
environment:
RABBITMQ_DEFAULT_USER: guest
RABBITMQ_DEFAULT_PASS: guest
networks:
- default
volumes:
app-data:
@ -15,4 +33,4 @@ volumes:
networks:
default:
name: production
external: true
external: true

1
go.mod
View File

@ -66,6 +66,7 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.55.0 // indirect
github.com/rabbitmq/amqp091-go v1.10.0
github.com/sagikazarmark/locafero v0.8.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.14.0 // indirect

2
go.sum
View File

@ -110,6 +110,8 @@ github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk=
github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U=
github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=
github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=