-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
450 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
// Copyright 2024 Google LLC | ||
// | ||
// 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" | ||
"fmt" | ||
"io" | ||
"log/slog" | ||
"sync" | ||
"time" | ||
) | ||
|
||
// ValueTextHandler is a [Handler] that writes Records to an [io.Writer] with values separated by spaces. | ||
type ValueTextHandler struct { | ||
h slog.Handler | ||
mu *sync.Mutex | ||
out io.Writer | ||
} | ||
|
||
// NewValueTextHandler creates a [ValueTextHandler] that writes to out, using the given options. | ||
func NewValueTextHandler(out io.Writer, opts *slog.HandlerOptions) *ValueTextHandler { | ||
if opts == nil { | ||
opts = &slog.HandlerOptions{} | ||
} | ||
return &ValueTextHandler{ | ||
out: out, | ||
h: slog.NewTextHandler(out, &slog.HandlerOptions{ | ||
Level: opts.Level, | ||
AddSource: opts.AddSource, | ||
ReplaceAttr: nil, | ||
}), | ||
mu: &sync.Mutex{}, | ||
} | ||
} | ||
|
||
func (h *ValueTextHandler) Enabled(ctx context.Context, level slog.Level) bool { | ||
return h.h.Enabled(ctx, level) | ||
} | ||
|
||
func (h *ValueTextHandler) WithAttrs(attrs []slog.Attr) slog.Handler { | ||
return &ValueTextHandler{h: h.h.WithAttrs(attrs), out: h.out, mu: h.mu} | ||
} | ||
|
||
func (h *ValueTextHandler) WithGroup(name string) slog.Handler { | ||
return &ValueTextHandler{h: h.h.WithGroup(name), out: h.out, mu: h.mu} | ||
} | ||
|
||
// Handle formats its argument [Record] as a single line of space-separated values. | ||
// Example output format: 2024-11-12T15:08:11.451377-08:00 INFO "Initalized 0 sources.\n" | ||
func (h *ValueTextHandler) Handle(ctx context.Context, r slog.Record) error { | ||
buf := make([]byte, 0, 1024) | ||
|
||
// time | ||
if !r.Time.IsZero() { | ||
buf = h.appendAttr(buf, slog.Time(slog.TimeKey, r.Time)) | ||
} | ||
// level | ||
buf = h.appendAttr(buf, slog.Any(slog.LevelKey, r.Level)) | ||
// message | ||
buf = h.appendAttr(buf, slog.String(slog.MessageKey, r.Message)) | ||
|
||
r.Attrs(func(a slog.Attr) bool { | ||
buf = h.appendAttr(buf, a) | ||
return true | ||
}) | ||
buf = append(buf, "\n"...) | ||
|
||
h.mu.Lock() | ||
defer h.mu.Unlock() | ||
_, err := h.out.Write(buf) | ||
return err | ||
} | ||
|
||
// appendAttr is reponsible for formatting a single attribute | ||
func (h *ValueTextHandler) appendAttr(buf []byte, a slog.Attr) []byte { | ||
// Resolve the Attr's value before doing anything else. | ||
a.Value = a.Value.Resolve() | ||
// Ignore empty Attrs. | ||
if a.Equal(slog.Attr{}) { | ||
return buf | ||
} | ||
switch a.Value.Kind() { | ||
case slog.KindString: | ||
// Quote string values, to make them easy to parse. | ||
buf = fmt.Appendf(buf, "%q ", a.Value.String()) | ||
case slog.KindTime: | ||
// Write times in a standard way, without the monotonic time. | ||
buf = fmt.Appendf(buf, "%s ", a.Value.Time().Format(time.RFC3339Nano)) | ||
case slog.KindGroup: | ||
attrs := a.Value.Group() | ||
// Ignore empty groups. | ||
if len(attrs) == 0 { | ||
return buf | ||
} | ||
for _, ga := range attrs { | ||
buf = h.appendAttr(buf, ga) | ||
} | ||
default: | ||
buf = fmt.Appendf(buf, "%s ", a.Value) | ||
} | ||
|
||
return buf | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
// Copyright 2024 Google LLC | ||
// | ||
// 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 ( | ||
"io" | ||
"log/slog" | ||
"strings" | ||
|
||
"github.com/googleapis/genai-toolbox/toolbox" | ||
) | ||
|
||
// StdLogger is the standard logger | ||
type StdLogger struct { | ||
outLogger *slog.Logger | ||
errLogger *slog.Logger | ||
} | ||
|
||
// NewStdLogger create a Logger that uses out and err for informational and error messages. | ||
func NewStdLogger(out, err io.Writer, logLevel string) toolbox.Logger { | ||
//Set log level | ||
var programLevel = new(slog.LevelVar) | ||
programLevel.Set(severityToLevel(logLevel)) | ||
|
||
handlerOptions := &slog.HandlerOptions{Level: programLevel} | ||
|
||
return &StdLogger{ | ||
outLogger: slog.New(NewValueTextHandler(out, handlerOptions)), | ||
errLogger: slog.New(NewValueTextHandler(err, handlerOptions)), | ||
} | ||
} | ||
|
||
// Debug logs debug messages | ||
func (sl *StdLogger) Debug(msg string, keysAndValues ...interface{}) { | ||
sl.outLogger.Debug(msg, keysAndValues...) | ||
} | ||
|
||
// Info logs debug messages | ||
func (sl *StdLogger) Info(msg string, keysAndValues ...interface{}) { | ||
sl.outLogger.Info(msg, keysAndValues...) | ||
} | ||
|
||
// Warn logs warning messages | ||
func (sl *StdLogger) Warn(msg string, keysAndValues ...interface{}) { | ||
sl.errLogger.Warn(msg, keysAndValues...) | ||
} | ||
|
||
// Error logs error messages | ||
func (sl *StdLogger) Error(msg string, keysAndValues ...interface{}) { | ||
sl.errLogger.Error(msg, keysAndValues...) | ||
} | ||
|
||
const ( | ||
Debug = "DEBUG" | ||
Info = "INFO" | ||
Warn = "WARN" | ||
Error = "ERROR" | ||
) | ||
|
||
// Returns severity level based on string. | ||
func severityToLevel(s string) slog.Level { | ||
switch strings.ToUpper(s) { | ||
case Debug: | ||
return slog.LevelDebug | ||
case Info: | ||
return slog.LevelInfo | ||
case Warn: | ||
return slog.LevelWarn | ||
default: | ||
return slog.LevelError | ||
} | ||
} |
Oops, something went wrong.