3333)
3434
3535type systemLogCursor struct {
36- EndTime int64 `json:"endTime"`
37- Skipped int `json:"skipped"`
36+ EndTime int64 `json:"endTime"`
37+ Skipped int `json:"skipped"`
38+ JournalCursor string `json:"journalCursor,omitempty"`
3839}
3940
4041type ILogService interface {
@@ -61,32 +62,14 @@ func (u *LogService) ReadSystemLog(req dto.SystemLogReq) (dto.SystemLogRes, erro
6162 if err != nil {
6263 return dto.SystemLogRes {}, err
6364 }
64- if cursor != nil {
65+ if cursor != nil && cursor . JournalCursor == "" {
6566 endTime = time .Unix (0 , cursor .EndTime * int64 (time .Microsecond ))
6667 }
6768 journalctl , err := exec .LookPath ("journalctl" )
6869 if err != nil {
6970 return u .readFileSystemLog (req , startTime , endTime , pageSize , cursor )
7071 }
71- args := []string {
72- "--no-pager" , "--reverse" , "--output=json" ,
73- "--since" , formatJournalQueryTime (startTime ),
74- "--until" , formatJournalQueryTime (endTime ),
75- }
76- if service := strings .TrimSpace (req .Service ); service != "" {
77- args = append (args , "-u" , service )
78- }
79- if priority := strings .TrimSpace (req .Priority ); priority != "" {
80- args = append (args , "--priority" , priority )
81- }
82- if keyword := strings .TrimSpace (req .Keyword ); keyword != "" {
83- args = append (args , "--grep" , keyword )
84- }
85- queryLines := pageSize + 1
86- if cursor != nil {
87- queryLines += cursor .Skipped
88- }
89- queryArgs := append (args , "--lines=" + strconv .Itoa (queryLines ))
72+ queryArgs := buildJournalQueryArgs (req , startTime , endTime , pageSize , cursor )
9073 ctx , cancel := context .WithTimeout (context .Background (), 15 * time .Second )
9174 output , err := exec .CommandContext (ctx , journalctl , queryArgs ... ).CombinedOutput ()
9275 cancel ()
@@ -98,7 +81,7 @@ func (u *LogService) ReadSystemLog(req dto.SystemLogReq) (dto.SystemLogRes, erro
9881 return dto.SystemLogRes {Source : "journalctl" , Items : []dto.SystemLogItem {}}, nil
9982 }
10083 items := parseJournalLogItems (content )
101- if cursor != nil {
84+ if cursor != nil && cursor . JournalCursor == "" {
10285 items , err = skipSystemLogCursorItems (items , * cursor )
10386 if err != nil {
10487 return dto.SystemLogRes {}, err
@@ -107,6 +90,32 @@ func (u *LogService) ReadSystemLog(req dto.SystemLogReq) (dto.SystemLogRes, erro
10790 return buildSystemLogResponse ("journalctl" , items , pageSize , cursor )
10891}
10992
93+ func buildJournalQueryArgs (req dto.SystemLogReq , startTime , endTime time.Time , pageSize int , cursor * systemLogCursor ) []string {
94+ args := []string {
95+ "--no-pager" , "--reverse" , "--output=json" ,
96+ "--since" , formatJournalQueryTime (startTime ),
97+ }
98+ if cursor != nil && cursor .JournalCursor != "" {
99+ args = append (args , "--after-cursor=" + cursor .JournalCursor )
100+ } else {
101+ args = append (args , "--until" , formatJournalQueryTime (endTime ))
102+ }
103+ if service := strings .TrimSpace (req .Service ); service != "" {
104+ args = append (args , "-u" , service )
105+ }
106+ if priority := strings .TrimSpace (req .Priority ); priority != "" {
107+ args = append (args , "--priority" , priority )
108+ }
109+ if keyword := strings .TrimSpace (req .Keyword ); keyword != "" {
110+ args = append (args , "--grep" , keyword )
111+ }
112+ queryLines := pageSize + 1
113+ if cursor != nil && cursor .JournalCursor == "" {
114+ queryLines += cursor .Skipped
115+ }
116+ return append (args , "--lines=" + strconv .Itoa (queryLines ))
117+ }
118+
110119func (u * LogService ) readFileSystemLog (req dto.SystemLogReq , startTime , endTime time.Time , pageSize int , cursor * systemLogCursor ) (dto.SystemLogRes , error ) {
111120 items := make ([]dto.SystemLogItem , 0 )
112121 for _ , logFile := range []string {"/var/log/syslog" , "/var/log/messages" , "/var/log/system.log" } {
@@ -156,11 +165,16 @@ func buildSystemLogResponse(source string, items []dto.SystemLogItem, pageSize i
156165 return res , nil
157166 }
158167 lastItem := items [len (items )- 1 ]
159- skipped := countSystemLogTimestamp (items , lastItem .Timestamp )
160- if cursor != nil && cursor .EndTime == lastItem .Timestamp {
161- skipped += cursor .Skipped
168+ next := systemLogCursor {EndTime : lastItem .Timestamp }
169+ if lastItem .Cursor != "" {
170+ next .JournalCursor = lastItem .Cursor
171+ } else {
172+ next .Skipped = countSystemLogTimestamp (items , lastItem .Timestamp )
173+ if cursor != nil && cursor .EndTime == lastItem .Timestamp {
174+ next .Skipped += cursor .Skipped
175+ }
162176 }
163- nextCursor , err := encodeSystemLogCursor (systemLogCursor { EndTime : lastItem . Timestamp , Skipped : skipped } )
177+ nextCursor , err := encodeSystemLogCursor (next )
164178 if err != nil {
165179 return dto.SystemLogRes {}, err
166180 }
@@ -215,7 +229,8 @@ func countSystemLogTimestamp(items []dto.SystemLogItem, timestamp int64) int {
215229}
216230
217231func formatJournalQueryTime (value time.Time ) string {
218- return value .In (time .Local ).Format ("2006-01-02 15:04:05.000000" )
232+ // Fractional seconds are rejected by some journalctl versions.
233+ return value .In (time .Local ).Format ("2006-01-02 15:04:05" )
219234}
220235
221236func parseFileSystemLogItem (line string ) (dto.SystemLogItem , bool ) {
@@ -358,6 +373,7 @@ func parseJournalLogItems(content string) []dto.SystemLogItem {
358373 }
359374 items = append (items , dto.SystemLogItem {
360375 Timestamp : journalFieldMicroseconds (fields ),
376+ Cursor : journalFieldString (fields , "__CURSOR" ),
361377 Time : formatJournalTimestamp (journalFieldString (fields , "__REALTIME_TIMESTAMP" )),
362378 Priority : journalFieldString (fields , "PRIORITY" ),
363379 Service : journalFieldString (fields , "_SYSTEMD_UNIT" ),
0 commit comments