Skip to content

Commit 3c9ca77

Browse files
committed
feat: Implement core application structure with multi-driver storage, API handlers, and a new web UI including dashboard, service map, logs, and traces explorers.
1 parent cc8b70b commit 3c9ca77

49 files changed

Lines changed: 4719 additions & 3039 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/audit.yml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
name: Security Audit
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
schedule:
9+
# Run weekly on Mondays at 09:00 UTC
10+
- cron: '0 9 * * 1'
11+
workflow_dispatch:
12+
13+
jobs:
14+
vulnerability-check:
15+
name: Go Vulnerability Check
16+
runs-on: ubuntu-latest
17+
steps:
18+
- name: Checkout code
19+
uses: actions/checkout@v4
20+
21+
- name: Setup Go
22+
uses: actions/setup-go@v5
23+
with:
24+
go-version: '1.24'
25+
26+
- name: Install govulncheck
27+
run: go install golang.org/x/vuln/cmd/govulncheck@latest
28+
29+
- name: Run govulncheck
30+
run: govulncheck ./...
31+
32+
build-check:
33+
name: Build Verification
34+
runs-on: ubuntu-latest
35+
steps:
36+
- name: Checkout code
37+
uses: actions/checkout@v4
38+
39+
- name: Setup Go
40+
uses: actions/setup-go@v5
41+
with:
42+
go-version: '1.24'
43+
44+
- name: Setup Node.js
45+
uses: actions/setup-node@v4
46+
with:
47+
node-version: '20'
48+
cache: 'npm'
49+
cache-dependency-path: web/package-lock.json
50+
51+
- name: Build Frontend
52+
working-directory: web
53+
run: |
54+
npm ci
55+
npm run build
56+
57+
- name: Go Vet
58+
run: go vet ./...
59+
60+
- name: Build Backend
61+
run: go build -o argus ./cmd/argus

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,4 +108,6 @@ dist
108108
*.out
109109
go.work
110110
go.work.sum
111-
.idea/
111+
.idea/
112+
113+
.dev-pids

cmd/argus/main.go

Lines changed: 97 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,26 @@ package main
22

33
import (
44
"context"
5+
"encoding/json"
56
"fmt"
67
"log"
8+
"log/slog"
79
"net"
810
"net/http"
911
"os"
1012
"os/signal"
13+
"strings"
1114
"syscall"
1215
"time"
1316

1417
"github.com/RandomCodeSpace/Project-Argus/internal/ai"
1518
"github.com/RandomCodeSpace/Project-Argus/internal/api"
1619
"github.com/RandomCodeSpace/Project-Argus/internal/config"
1720
"github.com/RandomCodeSpace/Project-Argus/internal/ingest"
21+
"github.com/RandomCodeSpace/Project-Argus/internal/queue"
22+
"github.com/RandomCodeSpace/Project-Argus/internal/realtime"
1823
"github.com/RandomCodeSpace/Project-Argus/internal/storage"
24+
"github.com/RandomCodeSpace/Project-Argus/internal/telemetry"
1925
"github.com/RandomCodeSpace/Project-Argus/web"
2026

2127
collogspb "go.opentelemetry.io/proto/otlp/collector/logs/v1"
@@ -30,42 +36,102 @@ func main() {
3036

3137
// 0. Load Configuration
3238
cfg := config.Load()
33-
log.Printf("🚀 Starting Argus in %s mode", cfg.Env)
3439

35-
// 1. Initialize Storage
36-
// repository.go already reads DB_DRIVER and DB_DSN from env,
37-
// which godotenv has now populated.
40+
// Initialize structured logger
41+
var level slog.Level
42+
switch strings.ToUpper(cfg.LogLevel) {
43+
case "DEBUG":
44+
level = slog.LevelDebug
45+
case "WARN":
46+
level = slog.LevelWarn
47+
case "ERROR":
48+
level = slog.LevelError
49+
default:
50+
level = slog.LevelInfo
51+
}
52+
53+
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
54+
Level: level,
55+
}))
56+
slog.SetDefault(logger)
57+
58+
slog.Info("🚀 Starting Argus V5.0", "env", cfg.Env, "log_level", level)
59+
60+
// 1. Initialize Internal Telemetry (first — everything registers metrics against this)
61+
metrics := telemetry.New()
62+
slog.Info("📊 Internal telemetry initialized")
63+
64+
// 2. Initialize Storage
3865
repo, err := storage.NewRepository()
3966
if err != nil {
4067
log.Fatalf("Failed to initialize repository: %v", err)
4168
}
69+
slog.Info("💾 Storage initialized", "driver", cfg.DBDriver)
70+
71+
// 3. Initialize DLQ (Dead Letter Queue)
72+
replayInterval, err := time.ParseDuration(cfg.DLQReplayInterval)
73+
if err != nil {
74+
replayInterval = 5 * time.Minute
75+
}
76+
77+
dlq, err := queue.NewDLQ(cfg.DLQPath, replayInterval, func(data []byte) error {
78+
// Replay handler: try to deserialize and re-insert logs
79+
var logs []storage.Log
80+
if err := json.Unmarshal(data, &logs); err != nil {
81+
return fmt.Errorf("DLQ replay unmarshal failed: %w", err)
82+
}
83+
return repo.BatchCreateLogs(logs)
84+
})
85+
if err != nil {
86+
log.Fatalf("Failed to initialize DLQ: %v", err)
87+
}
88+
defer dlq.Stop()
89+
slog.Info("🔁 DLQ initialized", "path", cfg.DLQPath, "interval", replayInterval)
90+
91+
// 4. Initialize Real-Time WebSocket Hub
92+
hub := realtime.NewHub(func(count int) {
93+
metrics.SetActiveConnections(count)
94+
})
95+
go hub.Run()
96+
defer hub.Stop()
97+
slog.Info("🔌 WebSocket hub started")
4298

43-
// 2. Initialize AI Service
99+
// 5. Initialize AI Service
44100
aiService := ai.NewService(repo)
45101
defer aiService.Stop()
46102

47-
// 3. Initialize API Server
48-
apiServer := api.NewServer(repo)
103+
// 6. Initialize API Server
104+
apiServer := api.NewServer(repo, hub, metrics)
49105

50-
// 4. Initialize OTLP Ingestion (gRPC)
51-
traceServer := ingest.NewTraceServer(repo)
52-
logsServer := ingest.NewLogsServer(repo)
106+
// 7. Initialize OTLP Ingestion (gRPC)
107+
traceServer := ingest.NewTraceServer(repo, cfg)
108+
logsServer := ingest.NewLogsServer(repo, cfg)
53109

54-
// Wire up live log streaming
55-
// When a log arrives, ingest -> api.BroadcastLog
110+
// Wire up live log streaming + AI + DLQ metrics
56111
logHandler := func(l storage.Log) {
57112
start := time.Now()
58113
apiServer.BroadcastLog(l)
59-
// AI Analysis Trigger
60114
aiService.EnqueueLog(l)
61115
if time.Since(start) > 100*time.Millisecond {
62-
log.Println("Slow broadcast/enqueue took", time.Since(start))
116+
slog.Warn("Slow broadcast/enqueue", "duration", time.Since(start))
63117
}
64118
}
65119

66120
logsServer.SetLogCallback(logHandler)
67121
traceServer.SetLogCallback(logHandler)
68122

123+
// Update DLQ size metric periodically
124+
go func() {
125+
ticker := time.NewTicker(30 * time.Second)
126+
defer ticker.Stop()
127+
for {
128+
select {
129+
case <-ticker.C:
130+
metrics.SetDLQSize(dlq.Size())
131+
}
132+
}
133+
}()
134+
69135
// Start gRPC Server
70136
lis, err := net.Listen("tcp", ":"+cfg.GRPCPort)
71137
if err != nil {
@@ -77,13 +143,13 @@ func main() {
77143
reflection.Register(grpcServer)
78144

79145
go func() {
80-
log.Printf("Starting gRPC OTLP receiver on :%s", cfg.GRPCPort)
146+
slog.Info("📡 gRPC OTLP receiver started", "port", cfg.GRPCPort)
81147
if err := grpcServer.Serve(lis); err != nil {
82148
log.Fatalf("Failed to serve gRPC: %v", err)
83149
}
84150
}()
85151

86-
// 5. Start HTTP Server
152+
// 8. Start HTTP Server
87153
mux := http.NewServeMux()
88154
apiServer.RegisterRoutes(mux)
89155

@@ -104,13 +170,12 @@ func main() {
104170

105171
f, err := distFS.Open(path)
106172
if err == nil {
107-
// File exists, serve it
108173
f.Close()
109174
fileServer.ServeHTTP(w, r)
110175
return
111176
}
112177

113-
// File does not exist, serve index.html (SPA catch-all)
178+
// SPA catch-all → serve index.html
114179
f, err = distFS.Open("index.html")
115180
if err != nil {
116181
http.Error(w, "index.html not found", http.StatusInternalServerError)
@@ -131,39 +196,40 @@ func main() {
131196
}
132197

133198
go func() {
134-
log.Printf("Starting HTTP Server on :%s", cfg.HTTPPort)
199+
slog.Info("🌐 HTTP server started", "port", cfg.HTTPPort)
135200
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
136201
log.Fatalf("HTTP server failed: %v", err)
137202
}
138203
}()
139204

140-
// 6. Graceful Shutdown
205+
// 9. Graceful Shutdown
141206
stop := make(chan os.Signal, 1)
142207
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
143208
<-stop
144209

145-
log.Println("Shutting down...")
210+
slog.Info("Shutting down ARGUS V5.0...")
146211

147-
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
212+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
148213
defer cancel()
149214

150215
grpcServer.GracefulStop()
151216
if err := srv.Shutdown(ctx); err != nil {
152-
log.Printf("HTTP server forced to shutdown: %v", err)
217+
slog.Error("HTTP server forced shutdown", "error", err)
153218
}
154219

155-
log.Println("Server exited")
220+
slog.Info("✅ ARGUS V5.0 shutdown complete")
156221
}
157222

158223
func printBanner() {
159224
banner := `
160-
_ ____ ____ _ _ ____ __ ______
161-
/ \ | _ \ / ___| | | / ___| \ \ / /___ \
162-
/ _ \ | |_) | | _| | | \___ \ \ \ / / __) |
163-
/ ___ \| _ <| |_| | |_| |___) | \ V / / __/
164-
/_/ \_\_| \_\\____|\___/|____/ \_/ |_____|
165-
166-
Project ARGUS V2: Enterprise Edition - All Systems Nominal
225+
_ ____ ____ _ _ ____ __ _____ ___
226+
/ \ | _ \ / ___| | | / ___| \ \ / / ___|| _ \
227+
/ _ \ | |_) | | _| | | \___ \ \ \ / /|___ \| | | |
228+
/ ___ \| _ <| |_| | |_| |___) | \ V / ___) | |_| |
229+
/_/ \_\_| \_\\____|\\___/|____/ \_/ |____/|___/
230+
231+
ARGUS V5.0 (DEV MODE) — Production Hardened Edition
232+
The Eye That Never Sleeps 👁️
167233
`
168234
fmt.Println(banner)
169235
}

0 commit comments

Comments
 (0)