Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/fsnotify/fsnotify v1.7.0
github.com/getkin/kin-openapi v0.131.0
github.com/ghodss/yaml v1.0.0
github.com/go-logr/logr v1.4.3
github.com/go-resty/resty/v2 v2.11.0
github.com/golang-migrate/migrate/v4 v4.17.0
github.com/google/uuid v1.5.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ github.com/getkin/kin-openapi v0.131.0 h1:NO2UeHnFKRYhZ8wg6Nyh5Cq7dHk4suQQr72a4p
github.com/getkin/kin-openapi v0.131.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
Expand Down
2 changes: 1 addition & 1 deletion pkg/eventstreams/eventstreams.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ func (esm *esManager[CT, DT]) initEventStream(
return nil, err
}

streamCtx := log.WithLogField(esm.bgCtx, "eventstream", *spec.ESFields().Name)
streamCtx := log.WithLogFields(esm.bgCtx, "eventstream", *spec.ESFields().Name)
es = &eventStream[CT, DT]{
bgCtx: streamCtx,
esm: esm,
Expand Down
2 changes: 1 addition & 1 deletion pkg/ffapi/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ func (hs *HandlerFactory) APIWrapper(handler HandlerFunction) http.HandlerFunc {
}
ctx = withRequestID(ctx, httpReqID)
ctx = withPassthroughHeaders(ctx, req, hs.PassthroughHeaders)
ctx = log.WithLogField(ctx, "httpreq", httpReqID)
ctx = log.WithLogFields(ctx, "httpreq", httpReqID)

req = req.WithContext(ctx)
defer cancel()
Expand Down
2 changes: 1 addition & 1 deletion pkg/ffresty/ffresty.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ func NewWithConfig(ctx context.Context, ffrestyConfig Config) (client *resty.Cli
}
rCtx = context.WithValue(rCtx, retryCtxKey{}, r)
// Create a request logger from the root logger passed into the client
rCtx = log.WithLogField(rCtx, "breq", r.id)
rCtx = log.WithLogFields(rCtx, "breq", r.id)
req.SetContext(rCtx)
}

Expand Down
34 changes: 33 additions & 1 deletion pkg/log/log.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright © 2022 Kaleido, Inc.
// Copyright © 2022 - 2025 Kaleido, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
Expand Down Expand Up @@ -40,6 +40,7 @@ func WithLogger(ctx context.Context, logger *logrus.Entry) context.Context {
return context.WithValue(ctx, ctxLogKey{}, logger)
}

// Deprecated: Use WithLogFields instead.
// WithLogField adds the specified field to the logger in the context
func WithLogField(ctx context.Context, key, value string) context.Context {
if len(value) > 61 {
Expand All @@ -48,6 +49,37 @@ func WithLogField(ctx context.Context, key, value string) context.Context {
return WithLogger(ctx, loggerFromContext(ctx).WithField(key, value))
}

// WithLogFields adds the specified fields to the logger in the context for structured logging. The key-value pairs must be provided in pairs.
func WithLogFields(ctx context.Context, keyValues ...string) context.Context {
if len(keyValues)%2 != 0 {
panic("odd number of key-value entry fields provided, cannot determine key-value pairs")
}

entry := loggerFromContext(ctx)
fields := logrus.Fields{}
for i := 0; i < len(keyValues); i += 2 {
key := keyValues[i]
value := keyValues[i+1]
if len(value) > 61 {
value = value[0:61] + "..."
}
fields[key] = value
}
return WithLogger(ctx, entry.WithFields(fields))
}

// WithFields adds the specified, structured fields to the logger in the context
func WithFields(ctx context.Context, fields map[string]string) context.Context {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we keep the interface consistent with above and instead of a map have keyValuePairs as well?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well so this one is meant to feel more logrus native, but did struggle with the names to indicate that...

entryFields := logrus.Fields{}
for key, value := range fields {
if len(value) > 61 {
value = value[0:61] + "..."
}
entryFields[key] = value
}
return WithLogger(ctx, loggerFromContext(ctx).WithFields(entryFields))
}

// LoggerFromContext returns the logger for the current context, or no logger if there is no context
func loggerFromContext(ctx context.Context) *logrus.Entry {
logger := ctx.Value(ctxLogKey{})
Expand Down
25 changes: 23 additions & 2 deletions pkg/log/log_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright © 2022 Kaleido, Inc.
// Copyright © 2022 - 2025 Kaleido, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
Expand Down Expand Up @@ -30,7 +30,7 @@ func TestLogContext(t *testing.T) {
}

func TestLogContextLimited(t *testing.T) {
ctx := WithLogField(context.Background(), "myfield", "0123456789012345678901234567890123456789012345678901234567890123456789")
ctx := WithLogFields(context.Background(), "myfield", "0123456789012345678901234567890123456789012345678901234567890123456789")
assert.Equal(t, "0123456789012345678901234567890123456789012345678901234567890...", L(ctx).Data["myfield"])
}

Expand Down Expand Up @@ -82,3 +82,24 @@ func TestSetFormattingJSONEnabled(t *testing.T) {

L(context.Background()).Infof("JSON logs")
}

func TestWithFields(t *testing.T) {
ctx := WithFields(context.Background(), map[string]string{
"myfield": "myvalue",
"myfield2": "myvalue2",
})
assert.Equal(t, "myvalue", L(ctx).Data["myfield"])
assert.Equal(t, "myvalue2", L(ctx).Data["myfield2"])
}

func TestWithLogFields(t *testing.T) {
ctx := WithLogFields(context.Background(), "myfield", "myvalue", "myfield2", "myvalue2")
assert.Equal(t, "myvalue", L(ctx).Data["myfield"])
assert.Equal(t, "myvalue2", L(ctx).Data["myfield2"])
}

func TestWithLogFieldsOddNumberOfFields(t *testing.T) {
assert.Panics(t, func() {
WithLogFields(context.Background(), "myfield", "myvalue", "myfield2")
})
}
114 changes: 114 additions & 0 deletions pkg/log/logr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright © 2022 - 2025 Kaleido, Inc.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Copyright © 2022 - 2025 Kaleido, Inc.
// Copyright © 2025 Kaleido, Inc.

//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package log

import (
"context"

"github.com/go-logr/logr"
"github.com/sirupsen/logrus"
)

// logr.LogSink duck type backed by firefly-common's logrus wrapper
type Sink struct {
name string
logger *logrus.Entry
ctx context.Context
}

// NewLogr creates a new logr.Logger backed by firefly-common's logrus wrapper
func NewLogr(ctx context.Context, name string) logr.Logger {
return logr.New(&Sink{
name: name,
ctx: ctx,
logger: L(ctx),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This L( is so miss leading, it's actually getting the logger from context - it was already there so no worries just annoying

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe I should be using the private impl that L is pointed to ? That would read better

})
}

// Init initializes the sink
func (l *Sink) Init(_ logr.RuntimeInfo) {
// Optional: store callDepth if needed

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

???

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah don't think we need to implement this for now, will leave a different comment

}

func (l *Sink) Enabled(level int) bool {
// Map logr V-levels to ff-common levels
// logr: V(0)=info, V(1)=debug, V(2+)=trace
switch level {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check for negative level?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't be possible for logr but it is is int so will be defensive

case 0:
return l.logger.Level >= logrus.InfoLevel
case 1:
return l.logger.Level >= logrus.DebugLevel
default:
return l.logger.Level >= logrus.TraceLevel
}
}

// Info logs an info message with the given keys and values. keysAndValues is not efficiently implemented, use WithValues instead
func (l *Sink) Info(level int, msg string, keysAndValues ...interface{}) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confused how with Sink we still allow passing level int ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see Debug etc... so maybe this function is actually called Logf ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aaah is this implementing just the interface ?? that makes sense then

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah this is all the logr interface - which doesn't have log levels just ints lol - its quite terse.

This is just so k8s libs log through our logging, so we can use our logging in k8s controllers w/o having two different loggers going

logger := L(l.buildContext(keysAndValues))

switch level {
case 0:
logger.Infof(msg)
case 1:
logger.Debugf(msg)
default:
logger.Tracef(msg)
}
}

// Error logs an error message with the given keys and values. keysAndValues is not efficiently implemented, use WithValues instead
func (l *Sink) Error(err error, msg string, keysAndValues ...interface{}) {
logger := L(l.buildContext(keysAndValues))
if err != nil {
logger = logger.WithError(err)
}
logger.Errorf(msg)
}

// WithValues adds the given keys and values to the logger
func (l *Sink) WithValues(keysAndValues ...interface{}) logr.LogSink {
ctx := l.buildContext(keysAndValues)
return &Sink{
name: l.name,
logger: L(ctx),
ctx: ctx,
}
}

// WithName adds the given name to the logger
func (l *Sink) WithName(name string) logr.LogSink {
newName := l.name
if len(newName) > 0 {
newName += "."
}
newName += name

return &Sink{
name: newName,
logger: l.logger.WithField("logger", newName),
ctx: l.ctx,
}
}

func (l *Sink) buildContext(keysAndValues []interface{}) context.Context {
fields := make(map[string]string)
for i := 0; i < len(keysAndValues); i += 2 {
fields[keysAndValues[i].(string)] = keysAndValues[i+1].(string)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to be a bit safer with this cast keysAndValues[i].(string) and throw some error?

}
return WithFields(l.ctx, fields)
}
37 changes: 37 additions & 0 deletions pkg/log/logr_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright © 2022 - 2025 Kaleido, Inc.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Copyright © 2022 - 2025 Kaleido, Inc.
// Copyright © 2025 Kaleido, Inc.

//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package log

import (
"context"
"errors"
"testing"
)

func TestLogr(t *testing.T) {
ctx := context.Background()
logger := NewLogr(ctx, "test")
logger.Info("test", "key", "value")

subLogger := logger.WithName("sub")
subLogger.Info("test", "key", "value")

subLogger = subLogger.WithValues("key2", "value2")
subLogger.Info("test", "key", "value")

subLogger.V(4).Error(errors.New("test"), "test", "key", "value")
}
2 changes: 1 addition & 1 deletion pkg/wsserver/wsconn.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ type WebSocketCommandMessage struct {
func newConnection(bgCtx context.Context, server *webSocketServer, conn *ws.Conn) *webSocketConnection {
id := fftypes.NewUUID().String()
wsc := &webSocketConnection{
ctx: log.WithLogField(bgCtx, "wsc", id),
ctx: log.WithLogFields(bgCtx, "wsc", id),
id: id,
server: server,
conn: conn,
Expand Down