Skip to content

Commit 0336961

Browse files
authored
Merge pull request #20 from thand-io/logging-page
added logging page to help with troubleshooting
2 parents f9dccb4 + d91913d commit 0336961

10 files changed

Lines changed: 693 additions & 21 deletions

File tree

internal/config/logger.go

Lines changed: 130 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,42 @@
11
package config
22

3-
import "github.com/sirupsen/logrus"
3+
import (
4+
"sync"
5+
"time"
46

5-
type thandLogger struct {
7+
"github.com/sirupsen/logrus"
8+
"github.com/thand-io/agent/internal/models"
9+
)
610

7-
// Create a stack to psuh on new events and pop off older ones
8-
// When an error/warn event is fired, flush the stack to the logger
9-
eventStack []*logrus.Entry
11+
type thandLogger struct {
12+
// Ring buffer for storing events
13+
eventBuffer []*models.LogEntry
14+
maxSize int
15+
currentPos int
16+
isFull bool
17+
mu sync.RWMutex
1018
}
1119

1220
func NewThandLogger() *thandLogger {
13-
return &thandLogger{}
21+
return &thandLogger{
22+
eventBuffer: make([]*models.LogEntry, 1000),
23+
maxSize: 1000,
24+
currentPos: 0,
25+
isFull: false,
26+
}
1427
}
1528

1629
func (t *thandLogger) Fire(entry *logrus.Entry) error {
30+
t.mu.Lock()
31+
defer t.mu.Unlock()
32+
33+
// Add to ring buffer
34+
t.eventBuffer[t.currentPos] = models.NewLogEntry(entry)
35+
t.currentPos = (t.currentPos + 1) % t.maxSize
1736

18-
// Push the new event onto the stack
19-
t.eventStack = append(t.eventStack, entry)
37+
if t.currentPos == 0 {
38+
t.isFull = true
39+
}
2040

2141
return nil
2242
}
@@ -27,13 +47,112 @@ func (t *thandLogger) Levels() []logrus.Level {
2747
logrus.FatalLevel,
2848
logrus.ErrorLevel,
2949
logrus.WarnLevel,
50+
logrus.InfoLevel,
51+
// logrus.DebugLevel,
52+
// logrus.TraceLevel,
3053
}
3154
}
3255

3356
func (t *thandLogger) Clear() {
34-
t.eventStack = []*logrus.Entry{}
57+
t.mu.Lock()
58+
defer t.mu.Unlock()
59+
60+
t.eventBuffer = make([]*models.LogEntry, t.maxSize)
61+
t.currentPos = 0
62+
t.isFull = false
3563
}
3664

37-
func (t *thandLogger) GetEvents() []*logrus.Entry {
38-
return t.eventStack
65+
func (t *thandLogger) GetEvents() []*models.LogEntry {
66+
t.mu.RLock()
67+
defer t.mu.RUnlock()
68+
69+
if !t.isFull {
70+
// Return only filled portion
71+
result := make([]*models.LogEntry, t.currentPos)
72+
copy(result, t.eventBuffer[:t.currentPos])
73+
return result
74+
}
75+
76+
// Return in chronological order (oldest first)
77+
result := make([]*models.LogEntry, t.maxSize)
78+
copy(result, t.eventBuffer[t.currentPos:])
79+
copy(result[t.maxSize-t.currentPos:], t.eventBuffer[:t.currentPos])
80+
return result
81+
}
82+
83+
func (t *thandLogger) GetRecentEvents(count int) []*models.LogEntry {
84+
events := t.GetEvents()
85+
if len(events) <= count {
86+
return events
87+
}
88+
return events[len(events)-count:]
89+
}
90+
91+
// LogFilter contains the filtering criteria for log events
92+
type LogFilter struct {
93+
// Filter by log levels (if empty, all levels are included)
94+
Levels []logrus.Level `json:"levels,omitempty"`
95+
// Filter events after this time (if nil, no time filter from start)
96+
Since *time.Time `json:"since,omitempty"`
97+
// Filter events before this time (if nil, no time filter to end)
98+
Until *time.Time `json:"until,omitempty"`
99+
// Maximum number of events to return (if 0, no limit)
100+
Limit int `json:"limit,omitempty"`
101+
}
102+
103+
// GetEventsWithFilter returns events that match the specified filter criteria
104+
func (t *thandLogger) GetEventsWithFilter(filter LogFilter) []*models.LogEntry {
105+
t.mu.RLock()
106+
defer t.mu.RUnlock()
107+
108+
allEvents := t.getEventsInternal()
109+
var filtered []*models.LogEntry
110+
111+
// Create a map for quick level lookup if levels are specified
112+
levelMap := make(map[logrus.Level]bool)
113+
if len(filter.Levels) > 0 {
114+
for _, level := range filter.Levels {
115+
levelMap[level] = true
116+
}
117+
}
118+
119+
for _, entry := range allEvents {
120+
// Filter by log level
121+
if len(filter.Levels) > 0 && !levelMap[entry.Level] {
122+
continue
123+
}
124+
125+
// Filter by time range
126+
if filter.Since != nil && entry.Time.Before(*filter.Since) {
127+
continue
128+
}
129+
if filter.Until != nil && entry.Time.After(*filter.Until) {
130+
continue
131+
}
132+
133+
filtered = append(filtered, entry)
134+
135+
// Apply limit if specified
136+
if filter.Limit > 0 && len(filtered) >= filter.Limit {
137+
break
138+
}
139+
}
140+
141+
return filtered
142+
}
143+
144+
// getEventsInternal returns events without additional locking (assumes caller has lock)
145+
func (t *thandLogger) getEventsInternal() []*models.LogEntry {
146+
if !t.isFull {
147+
// Return only filled portion
148+
result := make([]*models.LogEntry, t.currentPos)
149+
copy(result, t.eventBuffer[:t.currentPos])
150+
return result
151+
}
152+
153+
// Return in chronological order (oldest first)
154+
result := make([]*models.LogEntry, t.maxSize)
155+
copy(result, t.eventBuffer[t.currentPos:])
156+
copy(result[t.maxSize-t.currentPos:], t.eventBuffer[:t.currentPos])
157+
return result
39158
}

internal/config/model.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,10 @@ func (c *Config) TraverseAndEvaluateProviderSecrets(providerName string, node ma
496496
return nil, fmt.Errorf("provider '%s' not found", providerName)
497497
}
498498

499+
func (c *Config) GetEventsWithFilter(filter LogFilter) []*models.LogEntry {
500+
return c.logger.GetEventsWithFilter(filter)
501+
}
502+
499503
func (r *Config) GetWorkflowFromElevationRequest(elevationRequest *models.ElevateRequest) (*models.Workflow, error) {
500504

501505
if elevationRequest == nil {

internal/daemon/auth.go

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/gin-contrib/sessions"
1010
"github.com/gin-gonic/gin"
11+
"github.com/sirupsen/logrus"
1112
"github.com/thand-io/agent/internal/common"
1213
"github.com/thand-io/agent/internal/config"
1314
"github.com/thand-io/agent/internal/models"
@@ -25,11 +26,7 @@ func (s *Server) getAuthRequest(c *gin.Context) {
2526

2627
config := s.GetConfig()
2728

28-
if len(callback) == 0 {
29-
callback = config.GetLocalServerUrl()
30-
}
31-
32-
if strings.Compare(callback, config.GetLoginServerUrl()) == 0 {
29+
if len(callback) > 0 && strings.Compare(callback, config.GetLoginServerUrl()) == 0 {
3330
s.getErrorPage(c, http.StatusBadRequest, "Callback cannot be the login server")
3431
return
3532
}
@@ -127,12 +124,10 @@ func (s *Server) getAuthPage(c *gin.Context) {
127124
return
128125
}
129126

130-
config := s.GetConfig()
131-
132127
callback, foundCallback := c.GetQuery("callback")
133128

134129
if !foundCallback || len(callback) == 0 {
135-
callback = config.GetLocalServerUrl()
130+
logrus.Debug("Using local server URL as callback")
136131
}
137132

138133
data := AuthPageData{
@@ -200,7 +195,11 @@ func (s *Server) getAuthCallbackPage(c *gin.Context, auth models.AuthWrapper) {
200195
return
201196
}
202197

203-
s.renderHtml(c, "auth_callback.html", data)
198+
if len(auth.Callback) == 0 {
199+
c.Redirect(http.StatusTemporaryRedirect, "/")
200+
} else {
201+
s.renderHtml(c, "auth_callback.html", data)
202+
}
204203
}
205204

206205
func (s *Server) getLogoutPage(c *gin.Context) {

internal/daemon/logs.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package daemon
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/gin-gonic/gin"
7+
"github.com/thand-io/agent/internal/config"
8+
"github.com/thand-io/agent/internal/models"
9+
)
10+
11+
type LogPageData struct {
12+
config.TemplateData
13+
Logs []*models.LogEntry
14+
}
15+
16+
func (s *Server) getLogsPage(c *gin.Context) {
17+
18+
// Check if we have a valid user
19+
20+
if s.Config.IsServer() {
21+
_, err := s.getUser(c)
22+
if err != nil {
23+
s.getErrorPage(c, http.StatusUnauthorized, "Unauthorized: unable to get user for list of available roles", err)
24+
return
25+
}
26+
}
27+
28+
logs := s.Config.GetEventsWithFilter(config.LogFilter{
29+
Limit: 500,
30+
})
31+
32+
if s.canAcceptHtml(c) {
33+
34+
c.HTML(http.StatusOK, "logs.html", LogPageData{
35+
TemplateData: s.GetTemplateData(c),
36+
Logs: logs,
37+
})
38+
39+
} else {
40+
41+
c.JSON(http.StatusOK, gin.H{
42+
"logs": logs,
43+
})
44+
}
45+
46+
}

internal/daemon/server.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@ func (s *Server) Start() error {
180180

181181
router.Use(sessions.Sessions("thand", getSessionStore(s.GetConfig().GetSecret())))
182182

183+
// Set HTML template engine
184+
router.SetHTMLTemplate(s.TemplateEngine)
185+
183186
// Setup routes
184187
s.setupRoutes(router)
185188

@@ -306,6 +309,8 @@ func (s *Server) setupRoutes(router *gin.Engine) {
306309
})
307310
}
308311

312+
router.GET("/logs", s.getLogsPage)
313+
309314
// Server shows the server info and calls the local daemon
310315
// for session info. If in agent mode then this call just
311316
// shows local session info
@@ -320,6 +325,8 @@ func (s *Server) setupRoutes(router *gin.Engine) {
320325
api := router.Group(s.Config.GetApiBasePath())
321326
{
322327

328+
api.GET("/logs", s.getLogsPage)
329+
323330
if s.Config.IsAgent() || s.Config.IsClient() {
324331

325332
// Agent endpoints

internal/daemon/static/footer.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
<ul class="footer-links">
77
<li><a href="{{.Config.Server.Health.Path}}" class="footer-link">Health</a></li>
88
{{if .Config.Server.Metrics.Enabled}}<li><a href="{{.Config.Server.Metrics.Path}}" class="footer-link">Metrics</a></li>{{end}}
9+
<li><a href="/logs" class="footer-link">Logs</a></li>
910
<li>
1011
<!-- Place this tag where you want the button to render. -->
1112
<a class="github-button" href="https://github.com/thand-io/agent" data-color-scheme="no-preference: light; light: light; dark: dark;" data-size="large" data-show-count="true" aria-label="Star thand-io/agent on GitHub">Star</a>

0 commit comments

Comments
 (0)