Skip to content

Commit 4d1cb9e

Browse files
authored
feat: advanced logging
1 parent 13e3ca5 commit 4d1cb9e

14 files changed

Lines changed: 330 additions & 53 deletions

File tree

Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ GitCommit := $(shell git rev-parse HEAD)
33
LDFLAGS := "-s -w -X github.com/tschaefer/rpinfo/version.Version=$(Version) -X github.com/tschaefer/rpinfo/version.GitCommit=$(GitCommit)"
44

55
.PHONY: all
6-
all: fmt lint dist
6+
all: fmt lint test dist
77

88
.PHONY: fmt
99
fmt:
@@ -29,6 +29,10 @@ checksum:
2929
done && \
3030
cd ..
3131

32+
.PHONY: test
33+
test:
34+
test -z $(shell go test ./... 2>&1 >/dev/null || echo 1) || (echo "[WARN] Fix test issues" && exit 1)
35+
3236
.PHONY: clean
3337
clean:
3438
rm -rf bin

README.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,17 @@ Start the server on `localhost:8080` by default.
3131
```
3232
For further configuration, see the command-line options below.
3333

34-
| Flag | Description | Default |
35-
|-------------------|--------------------------------------|-------------|
36-
| `-H`, `--host` | Host to bind the server to | `localhost` |
37-
| `-p`, `--port` | Port to run the server on | `8080` |
38-
| `-a`, `--auth` | Enable bearer token authentication | `false` |
39-
| `-t`, `--token` | Bearer token used for authentication | |
40-
| `-m`, `--metrics` | Enable Prometheus metrics endpoint | `false` |
41-
| `-r`, `--redoc` | Enable ReDoc API documentation | `false` |
42-
| `-h`, `--help` | Show help for the server command | |
34+
| Flag | Description | Default |
35+
|----------------------|-------------------------------------------------|-------------|
36+
| `-H`, `--host` | Host to bind the server to | `localhost` |
37+
| `-p`, `--port` | Port to run the server on | `8080` |
38+
| `-a`, `--auth` | Enable bearer token authentication | `false` |
39+
| `-t`, `--token` | Bearer token used for authentication | |
40+
| `-m`, `--metrics` | Enable Prometheus metrics endpoint | `false` |
41+
| `-r`, `--redoc` | Enable ReDoc API documentation | `false` |
42+
| `-f`, `--log-format` | Set log format: `text`, `structured`, `json` | `text` |
43+
| `-l`, `--log-level` | Set log level: `debug`, `info`, `warn`, `error` | `info` |
44+
| `-h`, `--help` | Show help for the server command | |
4345

4446
Additional a systemd service file and environment file are provided in the
4547
[contrib directory](https://github.com/tschaefer/rpinfo/tree/main/contrib) for automatic startup on boot and management of the

cmd/server.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ func init() {
2323
serverCmd.Flags().StringP("token", "t", "", "Bearer Token for authentication")
2424
serverCmd.Flags().BoolP("metrics", "m", false, "Enable Prometheus metrics")
2525
serverCmd.Flags().BoolP("redoc", "r", false, "Enable ReDoc API documentation")
26+
serverCmd.Flags().StringP("log-format", "f", "text", "Log format (text, structured, json)")
27+
serverCmd.Flags().StringP("log-level", "l", "info", "Log level (debug, info, warn, error)")
2628

2729
rootCmd.AddCommand(serverCmd)
2830
}
@@ -36,6 +38,8 @@ func RunServerCmd(cmd *cobra.Command, args []string) error {
3638
config.Token, _ = cmd.Flags().GetString("token")
3739
config.Metrics, _ = cmd.Flags().GetBool("metrics")
3840
config.Redoc, _ = cmd.Flags().GetBool("redoc")
41+
config.LogFormat, _ = cmd.Flags().GetString("log-format")
42+
config.LogLevel, _ = cmd.Flags().GetString("log-level")
3943

4044
server.Run(config)
4145

go.mod

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,19 @@ module github.com/tschaefer/rpinfo
33
go 1.24.2
44

55
require (
6+
github.com/VictoriaMetrics/metrics v1.38.0
67
github.com/gorilla/mux v1.8.1
78
github.com/spf13/cobra v1.9.1
8-
github.com/VictoriaMetrics/metrics v1.38.0
9+
github.com/stretchr/testify v1.11.1
910
)
1011

1112
require (
13+
github.com/davecgh/go-spew v1.1.1 // indirect
1214
github.com/inconshreveable/mousetrap v1.1.0 // indirect
15+
github.com/pmezard/go-difflib v1.0.0 // indirect
1316
github.com/spf13/pflag v1.0.6 // indirect
1417
github.com/valyala/fastrand v1.1.0 // indirect
1518
github.com/valyala/histogram v1.2.0 // indirect
1619
golang.org/x/sys v0.15.0 // indirect
20+
gopkg.in/yaml.v3 v3.0.1 // indirect
1721
)

go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,28 @@
11
github.com/VictoriaMetrics/metrics v1.38.0 h1:1d0dRgVH8Nnu8dKMfisKefPC3q7gqf3/odyO0quAvyA=
22
github.com/VictoriaMetrics/metrics v1.38.0/go.mod h1:r7hveu6xMdUACXvB8TYdAj8WEsKzWB0EkpJN+RDtOf8=
33
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
4+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
5+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
46
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
57
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
68
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
79
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
10+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
11+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
812
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
913
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
1014
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
1115
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
1216
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
17+
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
18+
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
1319
github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8=
1420
github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
1521
github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ=
1622
github.com/valyala/histogram v1.2.0/go.mod h1:Hb4kBwb4UxsaNbbbh+RRz8ZR6pdodR57tzWUS3BUzXY=
1723
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
1824
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
25+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
1926
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
27+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
2028
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

server/handler/error.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ package handler
66

77
import (
88
"encoding/json"
9+
"log/slog"
910
"net/http"
1011

12+
"github.com/tschaefer/rpinfo/server/log"
1113
"github.com/tschaefer/rpinfo/version"
1214
)
1315

@@ -20,9 +22,11 @@ func JSONError(w http.ResponseWriter, status int, message string) {
2022
}
2123

2224
func NotFoundHandler(w http.ResponseWriter, r *http.Request) {
25+
go log.Request(r, http.StatusInternalServerError, slog.LevelError, "not found")
2326
JSONError(w, http.StatusNotFound, "not found")
2427
}
2528

2629
func MethodNotAllowedHandler(w http.ResponseWriter, r *http.Request) {
30+
go log.Request(r, http.StatusMethodNotAllowed, slog.LevelWarn, "method not allowed")
2731
JSONError(w, http.StatusMethodNotAllowed, "method not allowed")
2832
}

server/handler/handler.go

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,21 @@ import (
1111
"net/http"
1212
"strings"
1313

14+
"github.com/tschaefer/rpinfo/server/log"
1415
"github.com/tschaefer/rpinfo/vcgencmd"
1516
)
1617

1718
type Handle struct {
1819
Cmd vcgencmd.Exec
1920
}
2021

21-
func runCmd(h Handle, w http.ResponseWriter, args ...string) map[string]string {
22-
out := h.Cmd.Run(args...)
23-
if out == nil {
22+
func runCmd(h Handle, w http.ResponseWriter, r *http.Request, args ...string) map[string]string {
23+
out, err := h.Cmd.Run(args...)
24+
if err != nil {
2425
w.Header().Set("Content-Type", "application/json")
2526
w.WriteHeader(http.StatusInternalServerError)
27+
28+
go log.RequestError(r, http.StatusInternalServerError, err.Error())
2629
json.NewEncoder(w).Encode(map[string]string{"detail": "internal server error"})
2730
return nil
2831
}
@@ -31,46 +34,49 @@ func runCmd(h Handle, w http.ResponseWriter, args ...string) map[string]string {
3134
}
3235

3336
func (h Handle) Temperature(w http.ResponseWriter, r *http.Request) {
34-
temp := runCmd(h, w, "measure_temp")
37+
temp := runCmd(h, w, r, "measure_temp")
3538
if temp == nil {
3639
return
3740
}
3841

42+
go log.RequestInfo(r, http.StatusOK, "Fetched temperature")
3943
json.NewEncoder(w).Encode(temp)
4044
}
4145

4246
func (h Handle) Configuration(w http.ResponseWriter, r *http.Request) {
4347
options := []string{"int", "str"}
4448
config := make(map[string]string)
4549
for _, opt := range options {
46-
out := runCmd(h, w, "get_config", opt)
50+
out := runCmd(h, w, r, "get_config", opt)
4751
if out == nil {
4852
return
4953
}
5054

5155
maps.Copy(config, out)
5256
}
5357

58+
go log.RequestInfo(r, http.StatusOK, "Fetched configuration")
5459
json.NewEncoder(w).Encode(config)
5560
}
5661

5762
func (h Handle) Voltages(w http.ResponseWriter, r *http.Request) {
5863
options := []string{"core", "sdram_c", "sdram_i", "sdram_p"}
5964
voltages := make(map[string]string)
6065
for _, opt := range options {
61-
out := runCmd(h, w, "measure_volts", opt)
66+
out := runCmd(h, w, r, "measure_volts", opt)
6267
if out == nil {
6368
return
6469
}
6570

6671
voltages[opt] = out["volt"]
6772
}
6873

74+
go log.RequestInfo(r, http.StatusOK, "Fetched voltages")
6975
json.NewEncoder(w).Encode(voltages)
7076
}
7177

7278
func (h Handle) Throttled(w http.ResponseWriter, r *http.Request) {
73-
throttled := runCmd(h, w, "get_throttled")
79+
throttled := runCmd(h, w, r, "get_throttled")
7480
if throttled == nil {
7581
return
7682
}
@@ -84,6 +90,7 @@ func (h Handle) Throttled(w http.ResponseWriter, r *http.Request) {
8490
throttled["throttled"] = message
8591
}
8692

93+
go log.RequestInfo(r, http.StatusOK, "Fetched throttled status")
8794
json.NewEncoder(w).Encode(throttled)
8895
}
8996

@@ -95,7 +102,7 @@ func (h Handle) Clock(w http.ResponseWriter, r *http.Request) {
95102
}
96103
clock := make(map[string]string)
97104
for _, opt := range options {
98-
out := runCmd(h, w, "measure_clock", opt)
105+
out := runCmd(h, w, r, "measure_clock", opt)
99106
if out == nil {
100107
return
101108
}
@@ -111,5 +118,6 @@ func (h Handle) Clock(w http.ResponseWriter, r *http.Request) {
111118
clock[opt] = value()
112119
}
113120

121+
go log.RequestInfo(r, http.StatusOK, "Fetched clock rates")
114122
json.NewEncoder(w).Encode(clock)
115123
}

server/handler/handler_test.go

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Licensed under the MIT license, see LICENSE in the project root for details.
55
package handler
66

77
import (
8+
"fmt"
89
"net/http"
910
"net/http/httptest"
1011
"strings"
@@ -15,45 +16,45 @@ import (
1516

1617
type mockRunnerSuccess struct{}
1718

18-
func (m mockRunnerSuccess) Run(args ...string) map[string]string {
19+
func (m mockRunnerSuccess) Run(args ...string) (map[string]string, error) {
1920
switch args[0] {
2021
case "measure_temp":
21-
return map[string]string{"temp": "45.0'C"}
22+
return map[string]string{"temp": "45.0'C"}, nil
2223
case "measure_volts":
2324
switch args[1] {
2425
case "core":
25-
return map[string]string{"volt": "1.3500V"}
26+
return map[string]string{"volt": "1.3500V"}, nil
2627
case "sdram_c":
27-
return map[string]string{"volt": "1.2000V"}
28+
return map[string]string{"volt": "1.2000V"}, nil
2829
case "sdram_i":
29-
return map[string]string{"volt": "1.2000V"}
30+
return map[string]string{"volt": "1.2000V"}, nil
3031
case "sdram_p":
31-
return map[string]string{"volt": "1.2250V"}
32+
return map[string]string{"volt": "1.2250V"}, nil
3233
default:
33-
return nil
34+
return nil, nil
3435
}
3536
case "get_config":
36-
return map[string]string{"init_uart_clock": "0x2dc6c00", "overlay_prefix": "overlays/", "total_mem": "512"}
37+
return map[string]string{"init_uart_clock": "0x2dc6c00", "overlay_prefix": "overlays/", "total_mem": "512"}, nil
3738
case "get_throttled":
38-
return map[string]string{"throttled": "0x50000"}
39+
return map[string]string{"throttled": "0x50000"}, nil
3940
case "measure_clock":
4041
switch args[1] {
4142
case "arm":
42-
return map[string]string{"freq": "600000000"}
43+
return map[string]string{"freq": "600000000"}, nil
4344
case "core":
44-
return map[string]string{"freq": "250000000"}
45+
return map[string]string{"freq": "250000000"}, nil
4546
default:
46-
return map[string]string{"freq": "0"}
47+
return map[string]string{"freq": "0"}, nil
4748
}
4849
default:
49-
return nil
50+
return nil, nil
5051
}
5152
}
5253

5354
type mockRunnerError struct{}
5455

55-
func (m mockRunnerError) Run(args ...string) map[string]string {
56-
return nil
56+
func (m mockRunnerError) Run(args ...string) (map[string]string, error) {
57+
return nil, fmt.Errorf("command failed")
5758
}
5859

5960
func Test_TemperatureReturnsJSON(t *testing.T) {

server/handler/metrics.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"strconv"
1515

1616
"github.com/VictoriaMetrics/metrics"
17+
"github.com/tschaefer/rpinfo/server/log"
1718
"github.com/tschaefer/rpinfo/vcgencmd"
1819
"github.com/tschaefer/rpinfo/version"
1920
)
@@ -48,9 +49,12 @@ func Metrics(w http.ResponseWriter, r *http.Request) {
4849
var buffer bytes.Buffer
4950
rpi.WritePrometheus(&buffer)
5051
if _, err := w.Write(buffer.Bytes()); err != nil {
52+
53+
go log.RequestError(r, http.StatusInternalServerError, fmt.Sprintf("Failed to write metrics: %v", err))
5154
http.Error(w, "Failed to write metrics", http.StatusInternalServerError)
5255
return
5356
}
57+
go log.RequestInfo(r, http.StatusOK, "Served metrics")
5458
}
5559

5660
func clock(kind string) float64 {
@@ -102,5 +106,10 @@ func voltage(kind string) float64 {
102106

103107
func exec(args ...string) map[string]string {
104108
h := Handle{Cmd: vcgencmd.Cmd{}}
105-
return h.Cmd.Run(args...)
109+
out, err := h.Cmd.Run(args...)
110+
if err != nil {
111+
return nil
112+
}
113+
114+
return out
106115
}

0 commit comments

Comments
 (0)