Skip to content

Commit 220ba4b

Browse files
authored
feat(accesslog): access log (#161)
1 parent b31636b commit 220ba4b

19 files changed

+672
-35
lines changed

.golangci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ version: "2"
22

33
linters:
44
exclusions:
5+
paths:
6+
- test
57
rules:
68
- path: '(.+)_test\.go'
79
linters:

admin/api/api.go

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/webhookx-io/webhookx/db"
88
"github.com/webhookx-io/webhookx/db/query"
99
"github.com/webhookx-io/webhookx/dispatcher"
10+
"github.com/webhookx-io/webhookx/pkg/accesslog"
1011
"github.com/webhookx-io/webhookx/pkg/declarative"
1112
"github.com/webhookx-io/webhookx/pkg/errs"
1213
"github.com/webhookx-io/webhookx/pkg/tracing"
@@ -22,22 +23,24 @@ const (
2223
)
2324

2425
type API struct {
25-
cfg *config.Config
26-
log *zap.SugaredLogger
27-
DB *db.DB
28-
dispatcher *dispatcher.Dispatcher
29-
tracer *tracing.Tracer
30-
declarative *declarative.Declarative
26+
cfg *config.Config
27+
log *zap.SugaredLogger
28+
DB *db.DB
29+
dispatcher *dispatcher.Dispatcher
30+
tracer *tracing.Tracer
31+
declarative *declarative.Declarative
32+
accessLogger accesslog.AccessLogger
3133
}
3234

33-
func NewAPI(cfg *config.Config, db *db.DB, dispatcher *dispatcher.Dispatcher, tracer *tracing.Tracer) *API {
35+
func NewAPI(cfg *config.Config, db *db.DB, dispatcher *dispatcher.Dispatcher, tracer *tracing.Tracer, accessLogger accesslog.AccessLogger) *API {
3436
return &API{
35-
cfg: cfg,
36-
log: zap.S(),
37-
DB: db,
38-
dispatcher: dispatcher,
39-
tracer: tracer,
40-
declarative: declarative.NewDeclarative(db),
37+
cfg: cfg,
38+
log: zap.S(),
39+
DB: db,
40+
dispatcher: dispatcher,
41+
tracer: tracer,
42+
declarative: declarative.NewDeclarative(db),
43+
accessLogger: accessLogger,
4144
}
4245
}
4346

@@ -110,6 +113,10 @@ func (api *API) assert(err error) {
110113
// Handler returns a http.Handler
111114
func (api *API) Handler() http.Handler {
112115
r := mux.NewRouter()
116+
117+
if api.accessLogger != nil {
118+
r.Use(accesslog.NewMiddleware(api.accessLogger))
119+
}
113120
if api.tracer != nil {
114121
r.Use(otelhttp.NewMiddleware("api.admin"))
115122
}

app/app.go

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/webhookx-io/webhookx/dispatcher"
1313
"github.com/webhookx-io/webhookx/eventbus"
1414
"github.com/webhookx-io/webhookx/mcache"
15+
"github.com/webhookx-io/webhookx/pkg/accesslog"
1516
"github.com/webhookx-io/webhookx/pkg/cache"
1617
"github.com/webhookx-io/webhookx/pkg/log"
1718
"github.com/webhookx-io/webhookx/pkg/metrics"
@@ -147,13 +148,33 @@ func (app *Application) initialize() error {
147148

148149
// admin
149150
if cfg.Admin.IsEnabled() {
150-
handler := api.NewAPI(cfg, db, app.dispatcher, app.tracer).Handler()
151-
app.admin = admin.NewAdmin(cfg.Admin, handler)
151+
var accessLogger accesslog.AccessLogger
152+
if cfg.AccessLog.Enabled() {
153+
accessLogger, err = accesslog.NewAccessLogger("admin", accesslog.Options{
154+
File: cfg.AccessLog.File,
155+
Format: string(cfg.AccessLog.Format),
156+
})
157+
if err != nil {
158+
return err
159+
}
160+
}
161+
api := api.NewAPI(cfg, db, app.dispatcher, app.tracer, accessLogger)
162+
app.admin = admin.NewAdmin(cfg.Admin, api.Handler())
152163
}
153164

154165
// gateway
155166
if cfg.Proxy.IsEnabled() {
156-
app.gateway = proxy.NewGateway(&cfg.Proxy, db, app.dispatcher, app.metrics, app.tracer, app.bus)
167+
var accessLogger accesslog.AccessLogger
168+
if cfg.AccessLog.Enabled() {
169+
accessLogger, err = accesslog.NewAccessLogger("proxy", accesslog.Options{
170+
File: cfg.AccessLog.File,
171+
Format: string(cfg.AccessLog.Format),
172+
})
173+
if err != nil {
174+
return err
175+
}
176+
}
177+
app.gateway = proxy.NewGateway(&cfg.Proxy, db, app.dispatcher, app.metrics, app.tracer, app.bus, accessLogger)
157178
}
158179

159180
return nil

config.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
log:
66
file: /dev/stdout
77
level: info # supported values are debug, info, warn, and error.
8-
format: text # supported values are text and json
8+
format: text # supported values are "text" and "json"
9+
10+
access_log:
11+
file: /dev/stdout
12+
format: text # supported values are "text" and "json"
913

1014
database:
1115
host: localhost

config/access_log.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package config
2+
3+
import (
4+
"fmt"
5+
"slices"
6+
)
7+
8+
type AccessLogConfig struct {
9+
File string `yaml:"file" default:"/dev/stdout"`
10+
Format LogFormat `yaml:"format" default:"text"`
11+
}
12+
13+
func (cfg AccessLogConfig) Validate() error {
14+
if !slices.Contains([]LogFormat{LogFormatText, LogFormatJson}, cfg.Format) {
15+
return fmt.Errorf("invalid format: %s", cfg.Format)
16+
}
17+
return nil
18+
}
19+
20+
func (cfg AccessLogConfig) Enabled() bool {
21+
return cfg.File != ""
22+
}

config/config.go

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
uuid "github.com/satori/go.uuid"
77
"github.com/webhookx-io/webhookx/pkg/envconfig"
88
"gopkg.in/yaml.v3"
9+
"io"
910
"os"
1011
)
1112

@@ -16,14 +17,15 @@ var (
1617
)
1718

1819
type Config struct {
19-
Log LogConfig `yaml:"log" envconfig:"LOG"`
20-
Database DatabaseConfig `yaml:"database" envconfig:"DATABASE"`
21-
Redis RedisConfig `yaml:"redis" envconfig:"REDIS"`
22-
Admin AdminConfig `yaml:"admin" envconfig:"ADMIN"`
23-
Proxy ProxyConfig `yaml:"proxy" envconfig:"PROXY"`
24-
Worker WorkerConfig `yaml:"worker" envconfig:"WORKER"`
25-
Metrics MetricsConfig `yaml:"metrics" envconfig:"METRICS"`
26-
Tracing TracingConfig `yaml:"tracing" envconfig:"TRACING"`
20+
Log LogConfig `yaml:"log" envconfig:"LOG"`
21+
AccessLog AccessLogConfig `yaml:"access_log" envconfig:"ACCESS_LOG"`
22+
Database DatabaseConfig `yaml:"database" envconfig:"DATABASE"`
23+
Redis RedisConfig `yaml:"redis" envconfig:"REDIS"`
24+
Admin AdminConfig `yaml:"admin" envconfig:"ADMIN"`
25+
Proxy ProxyConfig `yaml:"proxy" envconfig:"PROXY"`
26+
Worker WorkerConfig `yaml:"worker" envconfig:"WORKER"`
27+
Metrics MetricsConfig `yaml:"metrics" envconfig:"METRICS"`
28+
Tracing TracingConfig `yaml:"tracing" envconfig:"TRACING"`
2729
}
2830

2931
func (cfg Config) String() string {
@@ -38,6 +40,9 @@ func (cfg Config) Validate() error {
3840
if err := cfg.Log.Validate(); err != nil {
3941
return err
4042
}
43+
if err := cfg.AccessLog.Validate(); err != nil {
44+
return err
45+
}
4146
if err := cfg.Database.Validate(); err != nil {
4247
return err
4348
}
@@ -90,7 +95,7 @@ func InitWithFile(filename string) (*Config, error) {
9095
defer f.Close()
9196
decoder := yaml.NewDecoder(f)
9297
err = decoder.Decode(&cfg)
93-
if err != nil {
98+
if err != nil && err != io.EOF {
9499
return nil, err
95100
}
96101

config/config_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package config
22

33
import (
4+
"encoding/json"
45
"errors"
56
"github.com/stretchr/testify/assert"
67
"testing"
@@ -271,8 +272,56 @@ func TestTracingConfig(t *testing.T) {
271272
}
272273
}
273274

275+
func TestAccessLogConfig(t *testing.T) {
276+
tests := []struct {
277+
desc string
278+
cfg AccessLogConfig
279+
expectedValidateErr error
280+
}{
281+
{
282+
desc: "sanity",
283+
cfg: AccessLogConfig{
284+
File: "/dev/stdout",
285+
Format: "text",
286+
},
287+
expectedValidateErr: nil,
288+
},
289+
{
290+
desc: "invalid format",
291+
cfg: AccessLogConfig{
292+
File: "/dev/stdout",
293+
Format: "",
294+
},
295+
expectedValidateErr: errors.New("invalid format: "),
296+
},
297+
{
298+
desc: "invalid format: x",
299+
cfg: AccessLogConfig{
300+
File: "/dev/stdout",
301+
Format: "x",
302+
},
303+
expectedValidateErr: errors.New("invalid format: x"),
304+
},
305+
}
306+
for _, test := range tests {
307+
actualValidateErr := test.cfg.Validate()
308+
assert.Equal(t, test.expectedValidateErr, actualValidateErr, "expected %v got %v", test.expectedValidateErr, actualValidateErr)
309+
}
310+
}
311+
274312
func TestConfig(t *testing.T) {
275313
cfg, err := Init()
276314
assert.Nil(t, err)
277315
assert.Nil(t, cfg.Validate())
316+
str := cfg.String()
317+
cfg2 := &Config{}
318+
err = json.Unmarshal([]byte(str), cfg2)
319+
assert.Nil(t, err)
320+
assert.Equal(t, cfg, cfg2)
321+
}
322+
323+
func TestInitWithFile(t *testing.T) {
324+
cfg, err := InitWithFile("./testdata/config-empty.yml")
325+
assert.Nil(t, err)
326+
assert.Nil(t, cfg.Validate())
278327
}

config/testdata/config-empty.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# this is an empty configuration

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ require (
2020
github.com/onsi/gomega v1.37.0
2121
github.com/pkg/errors v0.9.1
2222
github.com/redis/go-redis/v9 v9.8.0
23+
github.com/rs/zerolog v1.34.0
2324
github.com/satori/go.uuid v1.2.0
2425
github.com/segmentio/ksuid v1.0.4
2526
github.com/spf13/cobra v1.9.1
@@ -49,6 +50,8 @@ require (
4950
github.com/jackc/pgpassfile v1.0.0 // indirect
5051
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
5152
github.com/jackc/puddle/v2 v2.2.2 // indirect
53+
github.com/mattn/go-colorable v0.1.13 // indirect
54+
github.com/mattn/go-isatty v0.0.19 // indirect
5255
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
5356
go.opentelemetry.io/contrib/propagators/aws v1.35.0 // indirect
5457
go.opentelemetry.io/contrib/propagators/b3 v1.35.0 // indirect

go.sum

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3
2020
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
2121
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
2222
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
23+
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
2324
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
2425
github.com/creasty/defaults v1.8.0 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYKk=
2526
github.com/creasty/defaults v1.8.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
@@ -70,6 +71,7 @@ github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpv
7071
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
7172
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
7273
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
74+
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
7375
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
7476
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
7577
github.com/golang-migrate/migrate/v4 v4.18.3 h1:EYGkoOsvgHHfm5U/naS1RP/6PL/Xv3S4B/swMiAmDLs=
@@ -117,6 +119,11 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
117119
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
118120
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
119121
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
122+
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
123+
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
124+
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
125+
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
126+
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
120127
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
121128
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
122129
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
@@ -144,6 +151,9 @@ github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhi
144151
github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
145152
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
146153
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
154+
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
155+
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
156+
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
147157
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
148158
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
149159
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
@@ -215,6 +225,9 @@ golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
215225
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
216226
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
217227
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
228+
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
229+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
230+
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
218231
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
219232
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
220233
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=

0 commit comments

Comments
 (0)