Skip to content

Commit fb9db6f

Browse files
committed
go/runtime: Add log manager
1 parent cca0303 commit fb9db6f

30 files changed

Lines changed: 757 additions & 20 deletions

File tree

.changelog/6197.feature.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
go/runtime: Add log manager
2+
3+
Components can access logs of the bundles they deployed via the API as long
4+
as they have the proper `log_view` permissions.

docs/oasis-node/metrics.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,10 @@ oasis_p2p_topics | Gauge | Number of supported P2P topics. | | [p2p](https://gi
7474
oasis_registry_entities | Gauge | Number of registry entities. | | [registry](https://github.com/oasisprotocol/oasis-core/tree/master/go/registry/metrics.go)
7575
oasis_registry_nodes | Gauge | Number of registry nodes. | | [registry](https://github.com/oasisprotocol/oasis-core/tree/master/go/registry/metrics.go)
7676
oasis_registry_runtimes | Gauge | Number of registry runtimes. | | [registry](https://github.com/oasisprotocol/oasis-core/tree/master/go/registry/metrics.go)
77-
oasis_rhp_failures | Counter | Number of failed Runtime Host calls. | call | [runtime/host/protocol](https://github.com/oasisprotocol/oasis-core/tree/master/go/runtime/host/protocol/connection.go)
78-
oasis_rhp_latency | Summary | Runtime Host call latency (seconds). | call | [runtime/host/protocol](https://github.com/oasisprotocol/oasis-core/tree/master/go/runtime/host/protocol/connection.go)
79-
oasis_rhp_successes | Counter | Number of successful Runtime Host calls. | call | [runtime/host/protocol](https://github.com/oasisprotocol/oasis-core/tree/master/go/runtime/host/protocol/connection.go)
80-
oasis_rhp_timeouts | Counter | Number of timed out Runtime Host calls. | | [runtime/host/protocol](https://github.com/oasisprotocol/oasis-core/tree/master/go/runtime/host/protocol/connection.go)
77+
oasis_rhp_failures | Counter | Number of failed Runtime Host calls. | call | [runtime/host/protocol](https://github.com/oasisprotocol/oasis-core/tree/master/go/runtime/host/protocol/metrics.go)
78+
oasis_rhp_latency | Summary | Runtime Host call latency (seconds). | call | [runtime/host/protocol](https://github.com/oasisprotocol/oasis-core/tree/master/go/runtime/host/protocol/metrics.go)
79+
oasis_rhp_successes | Counter | Number of successful Runtime Host calls. | call | [runtime/host/protocol](https://github.com/oasisprotocol/oasis-core/tree/master/go/runtime/host/protocol/metrics.go)
80+
oasis_rhp_timeouts | Counter | Number of timed out Runtime Host calls. | | [runtime/host/protocol](https://github.com/oasisprotocol/oasis-core/tree/master/go/runtime/host/protocol/metrics.go)
8181
oasis_roothash_block_interval | Summary | Time between roothash blocks (seconds). | runtime | [roothash](https://github.com/oasisprotocol/oasis-core/tree/master/go/roothash/metrics.go)
8282
oasis_storage_failures | Counter | Number of storage failures. | call | [storage/api](https://github.com/oasisprotocol/oasis-core/tree/master/go/storage/api/metrics.go)
8383
oasis_storage_latency | Summary | Storage call latency (seconds). | call | [storage/api](https://github.com/oasisprotocol/oasis-core/tree/master/go/storage/api/metrics.go)

go/.golangci.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
linters-settings:
22
goconst:
33
ignore-tests: true
4-
ignore-strings: 'unix:.*'
4+
ignore-strings: "unix:.*"
55
goimports:
66
# Put local imports after 3rd-party packages.
77
local-prefixes: github.com/oasisprotocol/oasis-core
@@ -53,6 +53,7 @@ linters-settings:
5353
- github.com/tidwall/btree
5454
- github.com/tyler-smith/go-bip39
5555
- github.com/mdlayher/vsock
56+
- github.com/nxadm/tail
5657

5758
linters:
5859
disable-all: true

go/common/logging/filter.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package logging
2+
3+
import (
4+
"github.com/go-kit/log"
5+
)
6+
7+
// filterLogger is a logger wrapper that filters out specific keys.
8+
type filterLogger struct {
9+
logger log.Logger
10+
excludeFields map[any]struct{}
11+
}
12+
13+
func (l *filterLogger) Log(keyvals ...any) error {
14+
if len(keyvals) == 0 {
15+
return nil
16+
}
17+
18+
filteredKvs := make([]any, 0, len(keyvals))
19+
for i := 0; i < len(keyvals); i += 2 {
20+
if _, ok := l.excludeFields[keyvals[i]]; ok {
21+
continue
22+
}
23+
24+
filteredKvs = append(filteredKvs, keyvals[i])
25+
if i+1 < len(keyvals) {
26+
filteredKvs = append(filteredKvs, keyvals[i+1])
27+
}
28+
}
29+
30+
return l.logger.Log(filteredKvs...)
31+
}
32+
33+
// NewFilterLogger creates a logger wrapper that filters out specific keys.
34+
func NewFilterLogger(base *Logger, excludeFields map[any]struct{}) *Logger {
35+
return &Logger{
36+
logger: &filterLogger{
37+
logger: base.logger,
38+
excludeFields: excludeFields,
39+
},
40+
level: base.level,
41+
}
42+
}

go/common/logging/filter_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package logging
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestFilterLogger(t *testing.T) {
11+
require := require.New(t)
12+
13+
var buffer bytes.Buffer
14+
logger := NewJSONLogger(&buffer)
15+
logger = NewFilterLogger(logger, map[any]struct{}{
16+
"foo": {},
17+
})
18+
logger.Info("this is a test", "bar", 42, "foo", 17)
19+
20+
const expectedOutput = `{"bar":42,"level":"info","msg":"this is a test"}` + "\n"
21+
require.Equal(expectedOutput, buffer.String())
22+
}

go/common/logging/json.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package logging
2+
3+
import (
4+
"io"
5+
6+
"github.com/go-kit/log"
7+
)
8+
9+
// NewJSONLogger creates a new logger which logs JSON-serialized logs directly to the given writer.
10+
func NewJSONLogger(w io.Writer) *Logger {
11+
return &Logger{
12+
logger: log.NewJSONLogger(w),
13+
}
14+
}

go/common/logging/multi.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package logging
2+
3+
import (
4+
"errors"
5+
6+
"github.com/go-kit/log"
7+
)
8+
9+
type multiLogger struct {
10+
loggers []log.Logger
11+
}
12+
13+
func (l *multiLogger) Log(keyvals ...any) error {
14+
var mergedErr error
15+
for _, logger := range l.loggers {
16+
err := logger.Log(keyvals...)
17+
mergedErr = errors.Join(mergedErr, err)
18+
}
19+
return mergedErr
20+
}
21+
22+
// NewMultiLogger creates a new multi logger which logs to extra logging backends in addition to
23+
// the base one.
24+
func NewMultiLogger(base *Logger, extra ...*Logger) *Logger {
25+
loggers := make([]log.Logger, 0, len(extra)+1)
26+
loggers = append(loggers, base.logger)
27+
for _, l := range extra {
28+
loggers = append(loggers, l.logger)
29+
}
30+
31+
var logger log.Logger
32+
logger = &multiLogger{
33+
loggers: loggers,
34+
}
35+
if base.module != "" {
36+
logger = log.WithPrefix(logger, "module", base.module)
37+
}
38+
39+
return &Logger{
40+
logger: logger,
41+
level: base.level,
42+
module: base.module,
43+
}
44+
}

go/common/logging/multi_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package logging
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestMultiLogger(t *testing.T) {
11+
require := require.New(t)
12+
13+
var (
14+
bufferA bytes.Buffer
15+
bufferB bytes.Buffer
16+
)
17+
loggerA := NewJSONLogger(&bufferA)
18+
loggerB := NewJSONLogger(&bufferB)
19+
multi := NewMultiLogger(loggerA, loggerB)
20+
multi.Info("this is a test", "foo", 3)
21+
22+
const expectedOutput1 = `{"foo":3,"level":"info","msg":"this is a test"}` + "\n"
23+
require.Equal(expectedOutput1, bufferA.String())
24+
require.Equal(expectedOutput1, bufferB.String())
25+
26+
var bufferC bytes.Buffer
27+
loggerC := NewJSONLogger(&bufferC).With("module", "test")
28+
loggerC.module = "test"
29+
multi = NewMultiLogger(loggerC, loggerB)
30+
bufferB.Reset()
31+
multi.Info("this is another test", "foo", 42)
32+
33+
const expectedOutput2 = `{"foo":42,"level":"info","module":"test","msg":"this is another test"}` + "\n"
34+
require.Equal(expectedOutput2, bufferB.String())
35+
require.Equal(expectedOutput2, bufferC.String())
36+
}

go/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ require (
3737
github.com/libp2p/go-libp2p-pubsub v0.13.0
3838
github.com/mdlayher/vsock v1.2.1
3939
github.com/multiformats/go-multiaddr v0.14.0
40+
github.com/nxadm/tail v1.4.11
4041
github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a
4142
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7
4243
github.com/olekukonko/tablewriter v0.0.5

go/runtime/config/config.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ type Config struct {
159159

160160
// TDX is configuration specific to Intel TDX.
161161
TDX TdxConfig `yaml:"tdx,omitempty"`
162+
163+
// Log is the runtime log config.
164+
Log LogConfig `yaml:"log,omitempty"`
162165
}
163166

164167
// GetComponent returns the configuration for the given component
@@ -206,6 +209,20 @@ type TdxConfig struct {
206209
CidCount uint32 `yaml:"cid_count,omitempty"`
207210
}
208211

212+
// LogConfig is the runtime log configuration.
213+
type LogConfig struct {
214+
// MaxLogSize is the maximum log size in bytes.
215+
MaxLogSize int `yaml:"max_log_size,omitempty"`
216+
}
217+
218+
// Validate validates the log configuration for correctness.
219+
func (l *LogConfig) Validate() error {
220+
if l.MaxLogSize < 1024 {
221+
return fmt.Errorf("maximum log size must be at least 1024 bytes")
222+
}
223+
return nil
224+
}
225+
209226
// RuntimeConfig is the runtime configuration.
210227
type RuntimeConfig struct {
211228
// ID is the runtime identifier.
@@ -327,6 +344,9 @@ const (
327344

328345
// PermissionVolumeRemove is the permission that grants the component rights to remove volumes.
329346
PermissionVolumeRemove ComponentPermission = "volume_remove"
347+
348+
// PermissionLogView is the permission that grants the component rights to view logs.
349+
PermissionLogView ComponentPermission = "log_view"
330350
)
331351

332352
// NetworkingConfig is the networking configuration.
@@ -414,6 +434,10 @@ func (c *Config) Validate() error {
414434
return fmt.Errorf("cannot specify more than 128 instances for load balancing")
415435
}
416436

437+
if err := c.Log.Validate(); err != nil {
438+
return err
439+
}
440+
417441
for _, rt := range c.Runtimes {
418442
if err := rt.Validate(); err != nil {
419443
return err
@@ -522,5 +546,8 @@ func DefaultConfig() Config {
522546
CidStart: 0xA5150000,
523547
CidCount: 1024,
524548
},
549+
Log: LogConfig{
550+
MaxLogSize: 1024 * 1024,
551+
},
525552
}
526553
}

0 commit comments

Comments
 (0)