feat: integrate RabbitMQ for logging and enhance logger with RabbitMQ hook
Deploy Application / deploy (push) Failing after 6s
Details
Deploy Application / deploy (push) Failing after 6s
Details
This commit is contained in:
parent
5c27453315
commit
27c465342d
12
cmd/main.go
12
cmd/main.go
|
|
@ -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)
|
||||
|
|
|
|||
121
config/logger.go
121
config/logger.go
|
|
@ -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,
|
||||
// },
|
||||
// )
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
1
go.mod
|
|
@ -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
2
go.sum
|
|
@ -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=
|
||||
|
|
|
|||
Loading…
Reference in New Issue