joao-r-reis commented on code in PR #1755: URL: https://github.com/apache/cassandra-gocql-driver/pull/1755#discussion_r2143403507
########## logger.go: ########## @@ -28,33 +28,346 @@ import ( "bytes" "fmt" "log" + "net" + "strconv" + "strings" + "sync" ) -type StdLogger interface { - Print(v ...interface{}) - Printf(format string, v ...interface{}) - Println(v ...interface{}) -} +// Deprecated: use StructuredLogger instead +type StdLogger interface{} type nopLogger struct{} -func (n nopLogger) Print(_ ...interface{}) {} +func (n nopLogger) Error(_ string, _ ...LogField) {} + +func (n nopLogger) Warning(_ string, _ ...LogField) {} + +func (n nopLogger) Info(_ string, _ ...LogField) {} -func (n nopLogger) Printf(_ string, _ ...interface{}) {} +func (n nopLogger) Debug(_ string, _ ...LogField) {} -func (n nopLogger) Println(_ ...interface{}) {} +var nopLoggerSingleton = &nopLogger{} type testLogger struct { - capture bytes.Buffer + logLevel LogLevel + capture bytes.Buffer + mu sync.Mutex +} + +func newTestLogger(logLevel LogLevel) *testLogger { + return &testLogger{logLevel: logLevel} +} + +func (l *testLogger) Error(msg string, fields ...LogField) { + if LogLevelError <= l.logLevel { + l.write("ERR gocql: ", msg, fields) + } +} + +func (l *testLogger) Warning(msg string, fields ...LogField) { + if LogLevelWarn <= l.logLevel { + l.write("WRN gocql: ", msg, fields) + } +} + +func (l *testLogger) Info(msg string, fields ...LogField) { + if LogLevelInfo <= l.logLevel { + l.write("INF gocql: ", msg, fields) + } +} + +func (l *testLogger) Debug(msg string, fields ...LogField) { + if LogLevelDebug <= l.logLevel { + l.write("DBG gocql: ", msg, fields) + } +} + +func (l *testLogger) write(prefix string, msg string, fields []LogField) { + buf := bytes.Buffer{} + writeLogMsg(&buf, prefix, msg, fields) + l.mu.Lock() + defer l.mu.Unlock() + l.capture.WriteString(buf.String() + "\n") +} + +func (l *testLogger) String() string { + l.mu.Lock() + defer l.mu.Unlock() + return l.capture.String() +} + +type defaultLogger struct { + logLevel LogLevel +} + +// NewLogger creates an StructuredLogger that uses the standard library "log" package. +func NewLogger(logLevel LogLevel) StructuredLogger { + return &defaultLogger{logLevel: logLevel} +} + +func (l *defaultLogger) Error(msg string, fields ...LogField) { + if LogLevelError <= l.logLevel { + l.write("ERR gocql: ", msg, fields) + } +} + +func (l *defaultLogger) Warning(msg string, fields ...LogField) { + if LogLevelWarn <= l.logLevel { + l.write("WRN gocql: ", msg, fields) + } +} + +func (l *defaultLogger) Info(msg string, fields ...LogField) { + if LogLevelInfo <= l.logLevel { + l.write("INF gocql: ", msg, fields) + } +} + +func (l *defaultLogger) Debug(msg string, fields ...LogField) { + if LogLevelDebug <= l.logLevel { + l.write("DBG gocql: ", msg, fields) + } +} + +func (l *defaultLogger) write(prefix string, msg string, fields []LogField) { + buf := bytes.Buffer{} + writeLogMsg(&buf, prefix, msg, fields) + log.Println(buf.String()) +} + +func writeFields(buf *bytes.Buffer, fields []LogField) { + for i, field := range fields { + if i > 0 { + buf.WriteRune(' ') + } + buf.WriteString(field.Name) + buf.WriteRune('=') + buf.WriteString(field.Value.String()) + } +} + +func writeLogMsg(buf *bytes.Buffer, prefix string, msg string, fields []LogField) { + buf.WriteString(prefix) + buf.WriteString(msg) + buf.WriteRune(' ') + writeFields(buf, fields) +} + +type LogLevel int + +const ( + LogLevelDebug = LogLevel(5) + LogLevelInfo = LogLevel(4) + LogLevelWarn = LogLevel(3) + LogLevelError = LogLevel(2) + LogLevelNone = LogLevel(0) +) + +func (recv LogLevel) String() string { + switch recv { + case LogLevelDebug: + return "debug" + case LogLevelInfo: + return "info" + case LogLevelWarn: + return "warn" + case LogLevelError: + return "error" + case LogLevelNone: + return "none" + default: + // fmt.sprintf allocates so use strings.Join instead + temp := [2]string{"invalid level ", strconv.Itoa(int(recv))} + return strings.Join(temp[:], "") + } +} + +type LogField struct { + Name string + Value LogFieldValue +} + +func newLogField(name string, value LogFieldValue) LogField { + return LogField{ + Name: name, + Value: value, + } } -func (l *testLogger) Print(v ...interface{}) { fmt.Fprint(&l.capture, v...) } -func (l *testLogger) Printf(format string, v ...interface{}) { fmt.Fprintf(&l.capture, format, v...) } -func (l *testLogger) Println(v ...interface{}) { fmt.Fprintln(&l.capture, v...) } -func (l *testLogger) String() string { return l.capture.String() } +func newLogFieldIp(name string, value net.IP) LogField { + var str string + if value != nil { + str = value.String() + } + return newLogField(name, logFieldValueString(str)) +} + +func newLogFieldError(name string, value error) LogField { + var str string + if value != nil { + str = value.Error() + } + return newLogField(name, logFieldValueString(str)) +} + +func newLogFieldStringer(name string, value fmt.Stringer) LogField { + var str string + if value != nil { + str = value.String() + } + return newLogField(name, logFieldValueString(str)) +} + +func newLogFieldString(name string, value string) LogField { + return newLogField(name, logFieldValueString(value)) +} + +func newLogFieldInt(name string, value int) LogField { + return newLogField(name, logFieldValueInt64(int64(value))) +} + +func newLogFieldBool(name string, value bool) LogField { + return newLogField(name, logFieldValueBool(value)) +} + +type StructuredLogger interface { + Error(msg string, fields ...LogField) + Warning(msg string, fields ...LogField) + Info(msg string, fields ...LogField) + Debug(msg string, fields ...LogField) +} + +// A LogFieldValue can represent any Go value, but unlike type any, +// it can represent most small values without an allocation. +// The zero Value corresponds to nil. +type LogFieldValue struct { + num uint64 + any interface{} +} + +// LogFieldValueType is the type of a [LogFieldValue]. +type LogFieldValueType int + +// It's important that LogFieldTypeAny is 0 so that a zero Value represents nil. +const ( + LogFieldTypeAny LogFieldValueType = iota + LogFieldTypeBool + LogFieldTypeInt64 + LogFieldTypeString +) + +// LogFieldValueType returns v's LogFieldValueType. +func (v LogFieldValue) LogFieldValueType() LogFieldValueType { + switch x := v.any.(type) { + case LogFieldValueType: + return x + case string: + return LogFieldTypeString + default: + return LogFieldTypeAny + } +} + +func logFieldValueString(value string) LogFieldValue { + return LogFieldValue{num: uint64(len(value)), any: value} +} -type defaultLogger struct{} +func logFieldValueInt(v int) LogFieldValue { + return logFieldValueInt64(int64(v)) +} + +func logFieldValueInt64(v int64) LogFieldValue { + return LogFieldValue{num: uint64(v), any: LogFieldTypeInt64} Review Comment: Yeah it would be easier but this makes the struct more lightweight, it's implemented the same way in `slog`'s equivalent type. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: pr-unsubscr...@cassandra.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org --------------------------------------------------------------------- To unsubscribe, e-mail: pr-unsubscr...@cassandra.apache.org For additional commands, e-mail: pr-h...@cassandra.apache.org