Skip to main content

Backend Overview

Architecture

Backend CLM dibangun dengan Go 1.23 menggunakan standard library net/http.

Directory Structure

backend/
├── cmd/
│ └── api/
│ └── main.go # Entry point
├── internal/
│ ├── config/
│ │ └── config.go # Environment configuration
│ ├── server/
│ │ └── server.go # Routes, middleware, handlers
│ ├── handlers/ # HTTP handlers (11 modules)
│ │ ├── alerts.go
│ │ ├── dashboard.go
│ │ ├── logaudit.go
│ │ ├── monitoring.go
│ │ ├── monitors.go
│ │ ├── telegram.go
│ │ ├── infra.go
│ │ ├── servicemap.go
│ │ ├── k8s.go
│ │ ├── camel.go
│ │ └── healthcheck.go
│ ├── keycloak/
│ │ ├── client.go # Keycloak API client
│ │ └── verifier.go # JWT verification
│ ├── opensearch/
│ │ └── client.go # OpenSearch client
│ ├── database/
│ │ └── client.go # PostgreSQL client
│ └── prometheus/
│ └── client.go # Prometheus client
├── migrations/ # SQL migrations
├── docs/ # Swagger docs
├── Makefile # Build commands
├── Dockerfile
├── go.mod
└── go.sum

Key Files

FileDescription
cmd/api/main.goEntry point, server startup
internal/server/server.goRoute definitions, middleware stack
internal/config/config.goEnvironment-based configuration
internal/keycloak/JWT verification, user management
internal/opensearch/OpenSearch search client
internal/database/PostgreSQL RBAC client

Dependencies

// go.mod
module log-management/backend

go 1.23

require (
github.com/swaggo/http-swagger v1.3.4
github.com/swaggo/swag v1.16.4
github.com/joho/godotenv v1.5.1
github.com/lib/pq v1.10.9
)

Configuration

Environment Variables

# Server
PORT=8081
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173

# Keycloak
KEYCLOAK_BASE_URL=http://localhost:8080
KEYCLOAK_REALM=log-monitoring
KEYCLOAK_CLIENT_ID=backend-api
KEYCLOAK_CLIENT_SECRET=
KEYCLOAK_ADMIN_USER=admin
KEYCLOAK_ADMIN_PASSWORD=Admin123!

# OpenSearch
OPENSEARCH_BASE_URL=https://localhost:9200
OPENSEARCH_USERNAME=admin
OPENSEARCH_PASSWORD=MyStrong@OpenSearch2026!
OPENSEARCH_INDEXES=docker-logs,system-logs
OPENSEARCH_SKIP_TLS_VERIFY=true

# Database
DB_HOST=localhost
DB_PORT=5432
DB_USER=clm
DB_PASSWORD=ClmBjbs2024!
DB_NAME=clm_db

# Telegram
TELEGRAM_BOT_USERNAME=bjbs_poc_clm_alert_bot
N8N_BROADCAST_WEBHOOK_URL=https://n8n.example.com/webhook/broadcast

# Prometheus
PROMETHEUS_URL=http://localhost:9090

Config Struct

type Config struct {
Port string
KeycloakBaseURL string
Realm string
ClientID string
ClientSecret string
KeycloakAdminUser string
KeycloakAdminPassword string
AllowedOrigins []string
OpenSearchBaseURL string
OpenSearchUsername string
OpenSearchPassword string
OpenSearchIndexes []string
OpenSearchSkipTLSVerify bool
DBHost string
DBPort string
DBUser string
DBPassword string
DBName string
TelegramBotUsername string
N8NBroadcastWebhookURL string
N8NAPIKey string
PrometheusURL string
DefaultTenant string
DefaultApplication string
SwaggerHost string
DatacentersFile string
}

Middleware Stack

func (s *Server) Handler() http.Handler {
return s.withRequestID(s.withLogging(s.withCORS(s.mux)))
}

Middleware Order

  1. Request ID - Generate unique request ID
  2. Logging - Log method, path, status, duration
  3. CORS - Handle cross-origin requests
  4. Auth - JWT token validation (protected routes)
  5. Permission - RBAC permission check

Request ID Middleware

func (s *Server) withRequestID(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestID := r.Header.Get("X-Request-ID")
if requestID == "" {
requestID = generateUUID()
}
w.Header().Set("X-Request-ID", requestID)
ctx := context.WithValue(r.Context(), "requestID", requestID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}

Logging Middleware

func (s *Server) withLogging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
wrapped := &statusWriter{ResponseWriter: w, status: http.StatusOK}
next.ServeHTTP(wrapped, r)
duration := time.Since(start)

requestID := r.Context().Value("requestID").(string)
log.Printf("[%s] %s %s %d %v", requestID, r.Method, r.URL.Path, wrapped.status, duration)
})
}

CORS Middleware

func (s *Server) withCORS(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("Origin")
if s.originAllowed(origin) {
w.Header().Set("Access-Control-Allow-Origin", origin)
w.Header().Set("Vary", "Origin")
w.Header().Set("Access-Control-Allow-Credentials", "true")
w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Tenant-ID")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
}

if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return
}

next.ServeHTTP(w, r)
})
}

Auth Middleware

func (s *Server) withAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := bearerToken(r.Header.Get("Authorization"))
if token == "" {
writeError(w, http.StatusUnauthorized, "missing bearer token")
return
}

claims, err := s.verifier.Validate(token)
if err != nil {
writeError(w, http.StatusUnauthorized, "unauthorized")
return
}

// Extract tenant_id from claims
tenantID := s.cfg.DefaultTenant
if tid, ok := claims["tenant_id"].(string); ok && tid != "" {
tenantID = tid
}

ctx := contextWithClaims(r.Context(), claims)
ctx = context.WithValue(ctx, "tenant_id", tenantID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}

Permission Middleware

func (s *Server) requirePermission(appSlug, resource, action string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tenantID := tenantFromContext(r.Context())
username := usernameFromContext(r.Context())

allowed, err := s.db.CheckPermission(username, tenantID, appSlug, resource, action)
if err != nil {
writeError(w, http.StatusInternalServerError, "permission check failed")
return
}
if !allowed {
writeError(w, http.StatusForbidden, "permission denied")
return
}

next.ServeHTTP(w, r)
})
}
}

Handler Modules

11 Handler Modules

ModuleFileDescription
Alertsalerts.goAlert management
Dashboarddashboard.goDashboard metrics & charts
LogAuditlogaudit.goAudit log search & export
Monitoringmonitoring.goPipeline, DC status, throughput
Monitorsmonitors.goOpenSearch alerting monitors
Telegramtelegram.goTelegram bot integration
Infrainfra.goInfrastructure monitoring
ServiceMapservicemap.goAPM service map & traces
K8sk8s.goKubernetes cluster health
Camelcamel.goApache Camel integration
Healthcheckhealthcheck.goCollector health

Running Locally

# 1. Set environment variables
export PORT=8081
export KEYCLOAK_BASE_URL=http://localhost:8080
# ... other vars

# 2. Run
go run ./cmd/api

# 3. Access Swagger UI
open http://localhost:8081/swagger/

Build

# Build binary
go build -o bin/api ./cmd/api

# Run tests
go test ./...

# Generate swagger docs
swag init -g cmd/api/main.go -o docs

Next Steps