Skip to content

Commit

Permalink
Log events: allow custom timestamps
Browse files Browse the repository at this point in the history
  • Loading branch information
metachris committed Nov 21, 2024
1 parent 13c229a commit e9deed2
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 5 deletions.
27 changes: 24 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,31 @@ $ curl localhost:3535/logs
2024-10-23T12:04:07Z this is a test
```

## Actions
#### Event timestamps

Actions are shell commands that can be executed via API. The commands are defined in the config file,
see [systemapi-config.toml](./systemapi-config.toml) for examples.
By default, events are timestamped with the current time. However, the timestamp can be overridden by
providing a valid unix timestamp (in seconds or milliseconds) as first part of the message:

```bash
# Start the server
$ go run cmd/system-api/main.go

# Add regular event
$ echo "hello world" > pipe.fifo

# Add event with custom timestamp
$ echo "1634966400 this is a test" > pipe.fifo

# Query events
$ curl localhost:3535/logs
2024-11-21T19:48:04Z hello world
2021-10-23T05:20:00Z this is a test <--- custom timestamp on this entry
```

## Actions

Actions are shell commands that can be executed via API. The commands are defined in the config file,
see [systemapi-config.toml](./systemapi-config.toml) for examples.

Actions are recorded in the event log.

Expand Down
26 changes: 25 additions & 1 deletion systemapi/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"io"
"net/http"
"os"
"strconv"
"strings"
"sync"
"syscall"
Expand Down Expand Up @@ -205,11 +206,34 @@ func (s *Server) handleLivenessCheck(w http.ResponseWriter, r *http.Request) {
func (s *Server) addEvent(event Event) {
// Add event to the list and prune if necessary
s.eventsLock.Lock()
defer s.eventsLock.Unlock()

// Trim whitespace from the message
event.Message = strings.TrimSpace(event.Message)
if len(event.Message) == 0 {
return
}

// Check for custom timestamp. If the first part of the message is a timestamp, use it instead of the current time.
// To be a timestamp, this must be an integer with 10 characters for seconds or 13 for milliseconds.
timestampStr := strings.Fields(event.Message)[0] // split by whitespace and take the first part
timeInt, err := strconv.ParseInt(timestampStr, 10, 64)
if err == nil {
if len(timestampStr) == 10 {
// timestamp in seconds, update event
event.ReceivedAt = time.Unix(timeInt, 0).UTC()
event.Message = strings.TrimSpace(event.Message[len(timestampStr):])
} else if len(timestampStr) == 13 {
// timestamp in milliseconds, update event
event.ReceivedAt = time.UnixMilli(timeInt).UTC()
event.Message = strings.TrimSpace(event.Message[len(timestampStr):])
}
}

s.events = append(s.events, event)
if len(s.events) > s.cfg.General.LogMaxEntries {
s.events = s.events[1:]
}
s.eventsLock.Unlock()
}

func (s *Server) addInternalEvent(msg string) {
Expand Down
36 changes: 35 additions & 1 deletion systemapi/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ func TestBasicAuth(t *testing.T) {
}

func TestMaxEntries(t *testing.T) {
// Verify maximum number of log entries is working correctly
// Ensure maximum number of log entries is working correctly
maxEntries := 5

cfg := NewConfig()
Expand All @@ -177,3 +177,37 @@ func TestMaxEntries(t *testing.T) {
require.Equal(t, "4", srv.events[3].Message)
require.Equal(t, "5", srv.events[4].Message)
}

func TestAddEntryMessageParsing(t *testing.T) {
// Ensure that messages with timestamps are correctly parsed
srv := newTestServer(t)

testTime1 := time.Date(2021, 1, 2, 3, 4, 5, 0, time.UTC)
testTime2 := time.Date(2022, 1, 2, 3, 4, 6, 0, time.UTC)
testTime2TimestampSec := testTime2.Unix()
testTime2TimestampMs := testTime2.UnixMilli()

// Add messages
srv.addEvent(Event{ReceivedAt: testTime1, Message: "1"}) // regular message
srv.addEvent(Event{ReceivedAt: testTime1, Message: fmt.Sprintf("%d 2", testTime2TimestampSec)}) // custom timestamp
srv.addEvent(Event{ReceivedAt: testTime1, Message: fmt.Sprintf("%d \t 3 \t ", testTime2TimestampMs)}) // custom timestamp, with whitespace to test trimming

// Add empty messages to ensure they are ignored
srv.addEvent(Event{ReceivedAt: testTime1, Message: ""}) // empty message
srv.addEvent(Event{ReceivedAt: testTime1, Message: " \t "}) // empty message

// 3 proper entries were added
require.Len(t, srv.events, 3)

// Check entry 1 (regular message)
require.Equal(t, "1", srv.events[0].Message)
require.Equal(t, testTime1, srv.events[0].ReceivedAt)

// Check entry 2 (timestamp in seconds)
require.Equal(t, "2", srv.events[1].Message)
require.Equal(t, testTime2, srv.events[1].ReceivedAt)

// Check entry 3 (timestamp in milliseconds)
require.Equal(t, "3", srv.events[2].Message) // check that whitespace was trimmed
require.Equal(t, testTime2, srv.events[2].ReceivedAt)
}

0 comments on commit e9deed2

Please sign in to comment.