From 2e68773dfca072cb81f219fc3b97ad34fe9d9f94 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Mon, 11 Apr 2022 13:09:59 -0400 Subject: [PATCH 01/48] all: gofmt Gofmt to update doc comments to the new formatting. For golang/go#51082. Change-Id: Ic98f647623f234cf5d36309c6204683e151820d7 Reviewed-on: https://go-review.googlesource.com/c/example/+/399596 Run-TryBot: Russ Cox TryBot-Result: Gopher Robot Auto-Submit: Russ Cox Reviewed-by: Ian Lance Taylor --- gotypes/defsuses/main.go | 2 +- gotypes/hello/hello.go | 2 +- gotypes/hugeparam/main.go | 3 ++- gotypes/implements/main.go | 2 +- gotypes/lookup/lookup.go | 4 ++-- gotypes/nilfunc/main.go | 4 ++-- gotypes/pkginfo/main.go | 2 +- gotypes/skeleton/main.go | 3 ++- gotypes/typeandvalue/main.go | 2 +- gotypes/weave.go | 3 ++- 10 files changed, 15 insertions(+), 12 deletions(-) diff --git a/gotypes/defsuses/main.go b/gotypes/defsuses/main.go index 5347f48b..bdddc6c5 100644 --- a/gotypes/defsuses/main.go +++ b/gotypes/defsuses/main.go @@ -19,7 +19,7 @@ func main() { } ` -//!+ +// !+ func PrintDefsUses(fset *token.FileSet, files ...*ast.File) error { conf := types.Config{Importer: importer.Default()} info := &types.Info{ diff --git a/gotypes/hello/hello.go b/gotypes/hello/hello.go index 01a28623..431bcc1c 100644 --- a/gotypes/hello/hello.go +++ b/gotypes/hello/hello.go @@ -1,4 +1,4 @@ -//!+ +// !+ package main import "fmt" diff --git a/gotypes/hugeparam/main.go b/gotypes/hugeparam/main.go index 1474403a..80fc47e2 100644 --- a/gotypes/hugeparam/main.go +++ b/gotypes/hugeparam/main.go @@ -1,6 +1,7 @@ // The hugeparam command identifies by-value parameters that are larger than n bytes. // // Example: +// // $ ./hugeparams encoding/xml package main @@ -15,7 +16,7 @@ import ( "golang.org/x/tools/go/loader" ) -//!+ +// !+ var bytesFlag = flag.Int("bytes", 48, "maximum parameter size in bytes") var sizeof = (&types.StdSizes{8, 8}).Sizeof // the sizeof function diff --git a/gotypes/implements/main.go b/gotypes/implements/main.go index 70b4ee51..5c1fc994 100644 --- a/gotypes/implements/main.go +++ b/gotypes/implements/main.go @@ -10,7 +10,7 @@ import ( "log" ) -//!+input +// !+input const input = `package main type A struct{} diff --git a/gotypes/lookup/lookup.go b/gotypes/lookup/lookup.go index 4313e2e4..074eb722 100644 --- a/gotypes/lookup/lookup.go +++ b/gotypes/lookup/lookup.go @@ -11,7 +11,7 @@ import ( "strings" ) -//!+input +// !+input const hello = ` package main @@ -32,7 +32,7 @@ func main() { //!-input -//!+main +// !+main func main() { fset := token.NewFileSet() f, err := parser.ParseFile(fset, "hello.go", hello, parser.ParseComments) diff --git a/gotypes/nilfunc/main.go b/gotypes/nilfunc/main.go index 2dbb19c4..1e976d95 100644 --- a/gotypes/nilfunc/main.go +++ b/gotypes/nilfunc/main.go @@ -10,7 +10,7 @@ import ( "log" ) -//!+input +// !+input const input = `package main import "bytes" @@ -50,7 +50,7 @@ func main() { }) } -//!+ +// !+ // CheckNilFuncComparison reports unintended comparisons // of functions against nil, e.g., "if x.Method == nil {". func CheckNilFuncComparison(info *types.Info, n ast.Node) { diff --git a/gotypes/pkginfo/main.go b/gotypes/pkginfo/main.go index 957e01b7..15dbf10e 100644 --- a/gotypes/pkginfo/main.go +++ b/gotypes/pkginfo/main.go @@ -1,4 +1,4 @@ -//!+ +// !+ package main import ( diff --git a/gotypes/skeleton/main.go b/gotypes/skeleton/main.go index c3961e20..1e6ee827 100644 --- a/gotypes/skeleton/main.go +++ b/gotypes/skeleton/main.go @@ -2,6 +2,7 @@ // that implements the specified interface type. // // Example: +// // $ ./skeleton io ReadWriteCloser buffer // // *buffer implements io.ReadWriteCloser. // type buffer struct{ /* ... */ } @@ -24,7 +25,7 @@ import ( const usage = "Usage: skeleton " -//!+ +// !+ func PrintSkeleton(pkg *types.Package, ifacename, concname string) error { obj := pkg.Scope().Lookup(ifacename) if obj == nil { diff --git a/gotypes/typeandvalue/main.go b/gotypes/typeandvalue/main.go index 7b3b553e..c7fb8ed9 100644 --- a/gotypes/typeandvalue/main.go +++ b/gotypes/typeandvalue/main.go @@ -12,7 +12,7 @@ import ( "log" ) -//!+input +// !+input const input = ` package main diff --git a/gotypes/weave.go b/gotypes/weave.go index cfaa57b9..bf8264aa 100644 --- a/gotypes/weave.go +++ b/gotypes/weave.go @@ -2,7 +2,8 @@ // It builds a table of contents and processes %include directives. // // Example usage: -// $ go run weave.go go-types.md > README.md +// +// $ go run weave.go go-types.md > README.md package main import ( From 1d341ffebc2deb0cfb012d6b2fed484ea70e09ab Mon Sep 17 00:00:00 2001 From: Jonathan Amsterdam Date: Thu, 11 May 2023 17:28:35 -0400 Subject: [PATCH 02/48] cmd/weave: move weave to a common location Move the weave program out of the gotypes directory so it can be used by other example documents. It will soon be used for a document on writing slog handlers. Change-Id: I38255e276e744e323695afaa66830107aa17d4d1 Reviewed-on: https://go-review.googlesource.com/c/example/+/494441 TryBot-Result: Gopher Robot Run-TryBot: Jonathan Amsterdam Reviewed-by: Alan Donovan --- gotypes/Makefile | 4 ++-- {gotypes => internal/cmd/weave}/weave.go | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename {gotypes => internal/cmd/weave}/weave.go (100%) diff --git a/gotypes/Makefile b/gotypes/Makefile index 8d40c92e..4bed56d0 100644 --- a/gotypes/Makefile +++ b/gotypes/Makefile @@ -2,8 +2,8 @@ all: README.md go build ./... -README.md: go-types.md weave.go $(wildcard */*.go) - go run weave.go go-types.md >README.md +README.md: go-types.md ../internal/cmd/weave/*.go $(wildcard */*.go) + go run ../internal/cmd/weave go-types.md >README.md # This is for previewing using github. # $HOME/markdown must be a github client. diff --git a/gotypes/weave.go b/internal/cmd/weave/weave.go similarity index 100% rename from gotypes/weave.go rename to internal/cmd/weave/weave.go From 61060d64ef5ae63c97dca5d93bb30dd7c1f2e2fa Mon Sep 17 00:00:00 2001 From: Jonathan Amsterdam Date: Fri, 12 May 2023 09:28:51 -0400 Subject: [PATCH 03/48] gotypes: re-make README.md Regenerate README.md from go-types.md. There are minor changes, mostly removals of line-ending spaces. Change-Id: I83ba4b385de44e0dd2c61f5f3019d08a24ebad29 Reviewed-on: https://go-review.googlesource.com/c/example/+/494595 TryBot-Result: Gopher Robot Run-TryBot: Jonathan Amsterdam Reviewed-by: Alan Donovan --- gotypes/README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/gotypes/README.md b/gotypes/README.md index f625f6b6..ee12bef9 100644 --- a/gotypes/README.md +++ b/gotypes/README.md @@ -36,9 +36,9 @@ This document is maintained by Alan Donovan `adonovan@google.com`. # Changes in Go 1.18 -Go 1.18 introduces generic Go code, and several corresponding new APIs for -`go/types`. This document is not yet up-to-date for these changes, but a guide -to the new changes exists at +Go 1.18 introduces generics, and several corresponding new APIs for `go/types`. +This document is not yet up-to-date for these changes, but a guide to the new +changes exists at [`x/exp/typeparams/example`](https://github.com/golang/exp/tree/master/typeparams/example). # Introduction @@ -224,7 +224,7 @@ how to locate the imported packages. Here we use `importer.Default()`, which loads compiler-generated export data, but we'll explore alternatives in [Imports](#imports). - + Fourth, the program calls `Check`. This creates a `Package` whose path is `"cmd/hello"`, and @@ -734,7 +734,7 @@ by calling its `(*Func).Scope` method. @@ -830,7 +830,7 @@ does a name lookup at a specific position in that lexical block. -A typical input is shown below. +A typical input is shown below. The first comment causes a lookup of `"append"` in the file block. The second comment looks up `"fmt"` in the `main` function's block, and so on. @@ -1185,7 +1185,7 @@ The type checker builds the exact same data structures given this input: A similar issue applies to the methods of named interface types. - + ## Tuple Types @@ -1419,7 +1419,7 @@ interface `v`, then the type assertion is not legal, as in this example: // error: io.Writer is not assertible to int - func f(w io.Writer) int { return w.(int) } + func f(w io.Writer) int { return w.(int) } @@ -1754,7 +1754,7 @@ all.) The final two parameters of `LookupFieldOrMethod` are `(pkg -*Package, name string)`. +*Package, name string)`. Together they specify the name of the field or method to look up. This brings us to `Id`s. @@ -2066,7 +2066,7 @@ Constants are represented using the `Value` interface from the package constant // go/constant type Value interface { - Kind() Kind + Kind() Kind } type Kind int // one of Unknown, Bool, String, Int, Float, Complex From 5bec756976671f30903223ec46ff8a70dced4954 Mon Sep 17 00:00:00 2001 From: Jonathan Amsterdam Date: Fri, 12 May 2023 09:31:27 -0400 Subject: [PATCH 04/48] internal/cmd/weave: minor improvements - Add some documentation. - Check bufio.Scanner errors. - Generalize the package path in caption output. Change-Id: I1f30b555ec8533464fb29af05fdcb0c5d926c18e Reviewed-on: https://go-review.googlesource.com/c/example/+/494596 TryBot-Result: Gopher Robot Run-TryBot: Jonathan Amsterdam Reviewed-by: Alan Donovan --- internal/cmd/weave/weave.go | 42 ++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/internal/cmd/weave/weave.go b/internal/cmd/weave/weave.go index bf8264aa..07ed8bfe 100644 --- a/internal/cmd/weave/weave.go +++ b/internal/cmd/weave/weave.go @@ -3,7 +3,25 @@ // // Example usage: // -// $ go run weave.go go-types.md > README.md +// $ go run internal/cmd/weave go-types.md > README.md +// +// The weave command copies lines of the input file to standard output, with two +// exceptions: +// +// If a line begins with "%toc", it is replaced with a table of contents +// consisting of links to the top two levels of headers ("#" and "##"). +// +// If a line begins with "%include FILENAME TAG", it is replaced with the lines +// of the file between lines containing "!+TAG" and "!-TAG". TAG can be omitted, +// in which case the delimiters are simply "!+" and "!-". +// +// Before the included lines, a line of the form +// +// // go get PACKAGE +// +// is output, where PACKAGE is constructed from the module path, the +// base name of the current directory, and the directory of FILENAME. +// This caption can be supressed by putting "-" as the final word of the %include line. package main import ( @@ -30,9 +48,15 @@ func main() { } defer f.Close() + wd, err := os.Getwd() + if err != nil { + log.Fatal(err) + } + curDir := filepath.Base(wd) + fmt.Println("") - // Pass 1. + // Pass 1: extract table of contents. var toc []string in := bufio.NewScanner(f) for in.Scan() { @@ -56,6 +80,9 @@ func main() { toc = append(toc, line) } } + if in.Err() != nil { + log.Fatal(in.Err()) + } // Pass 2. if _, err := f.Seek(0, os.SEEK_SET); err != nil { @@ -78,8 +105,8 @@ func main() { // Show caption unless '-' follows. if len(words) < 4 || words[3] != "-" { - fmt.Printf(" // go get golang.org/x/example/gotypes/%s\n\n", - filepath.Dir(filename)) + fmt.Printf(" // go get golang.org/x/example/%s/%s\n\n", + curDir, filepath.Dir(filename)) } section := "" @@ -96,7 +123,9 @@ func main() { default: fmt.Println(line) } - + } + if in.Err() != nil { + log.Fatal(in.Err()) } } @@ -135,6 +164,9 @@ func include(file, tag string) (string, error) { text.WriteByte('\n') } } + if in.Err() != nil { + return "", in.Err() + } if text.Len() == 0 { return "", fmt.Errorf("no lines of %s matched tag %q", file, tag) } From d23394f89d700d50fd2e48483149cb148097b412 Mon Sep 17 00:00:00 2001 From: Jonathan Amsterdam Date: Thu, 13 Jul 2023 07:21:57 -0400 Subject: [PATCH 05/48] go.mod: change go version to 1.18 This will enable generics in examples for the slog handler guide. Change-Id: I8fe2d876961899c15511226d29e1e5c2ef72519b Reviewed-on: https://go-review.googlesource.com/c/example/+/509100 Reviewed-by: Alan Donovan TryBot-Result: Gopher Robot Run-TryBot: Jonathan Amsterdam --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index c6b3f6b0..80b7ee1a 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module golang.org/x/example -go 1.15 +go 1.18 require golang.org/x/tools v0.0.0-20210112183307-1e6ecd4bf1b0 From 83a29069fa8045f780b114a3b7ac7bb88b041ffb Mon Sep 17 00:00:00 2001 From: Jonathan Amsterdam Date: Fri, 14 Jul 2023 07:37:12 -0400 Subject: [PATCH 06/48] slog-handler-guide/README.md: stub Add a file that will ultimately contain a guide to writing slog handlers. This CL just creates the file so we can make a shortlink to it. Change-Id: Ic31e62c6d92a480ecb7ba03cf052ca375f5abb94 Reviewed-on: https://go-review.googlesource.com/c/example/+/509735 TryBot-Result: Gopher Robot Run-TryBot: Jonathan Amsterdam Reviewed-by: Alan Donovan --- slog-handler-guide/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 slog-handler-guide/README.md diff --git a/slog-handler-guide/README.md b/slog-handler-guide/README.md new file mode 100644 index 00000000..d4a1d2ef --- /dev/null +++ b/slog-handler-guide/README.md @@ -0,0 +1,3 @@ +# A Guide to Writing `slog` Handlers + +This file will contain a guide to writing a slog handler. From 72e55f1f1d3a45c74af107258f35ade8f91d736f Mon Sep 17 00:00:00 2001 From: Jonathan Amsterdam Date: Thu, 11 May 2023 14:55:30 -0400 Subject: [PATCH 07/48] slog-handler-guide: guide to writing a slog Handler Add a document for helping writers of slog handlers. Change-Id: I079b738d1109a5e1d555c5dfb81eabf1d187f907 Reviewed-on: https://go-review.googlesource.com/c/example/+/494576 TryBot-Result: Gopher Robot Run-TryBot: Jonathan Amsterdam Reviewed-by: Ian Cottrell --- README.md | 6 + slog-handler-guide/Makefile | 13 ++ slog-handler-guide/README.md | 352 ++++++++++++++++++++++++++++++++++- slog-handler-guide/guide.md | 342 ++++++++++++++++++++++++++++++++++ 4 files changed, 712 insertions(+), 1 deletion(-) create mode 100644 slog-handler-guide/Makefile create mode 100644 slog-handler-guide/guide.md diff --git a/README.md b/README.md index 13b9972f..2dad8742 100644 --- a/README.md +++ b/README.md @@ -73,3 +73,9 @@ or manipulate Go programs. A trivial web server that demonstrates the use of the [`template` package](https://golang.org/pkg/text/template/)'s `block` feature. + +## [slog-handler-guide](slog-handler-guide/) + +The `log/slog` package supports structured logging. +It features a flexible backend in the form of a `Handler` interface. +This guide can help you write your own handler. diff --git a/slog-handler-guide/Makefile b/slog-handler-guide/Makefile new file mode 100644 index 00000000..4c1fe439 --- /dev/null +++ b/slog-handler-guide/Makefile @@ -0,0 +1,13 @@ + +all: README.md + go build ./... + +README.md: guide.md ../internal/cmd/weave/*.go $(wildcard */*.go) + go run ../internal/cmd/weave guide.md >README.md + +# This is for previewing using github. +# $HOME/markdown must be a github client. +test: README.md + cp README.md $$HOME/markdown/ + (cd $$HOME/markdown/ && git commit -m . README.md && git push) + diff --git a/slog-handler-guide/README.md b/slog-handler-guide/README.md index d4a1d2ef..7245e7e9 100644 --- a/slog-handler-guide/README.md +++ b/slog-handler-guide/README.md @@ -1,3 +1,353 @@ + + # A Guide to Writing `slog` Handlers -This file will contain a guide to writing a slog handler. +This document is maintained by Jonathan Amsterdam `jba@google.com`. + + +# Contents + +1. [Introduction](#introduction) +1. [Loggers and their handlers](#loggers-and-their-handlers) +1. [Implementing `Handler` methods](#implementing-`handler`-methods) + 1. [The `Enabled` method](#the-`enabled`-method) + 1. [The `WithAttrs` method](#the-`withattrs`-method) + 1. [The `WithGroup` method](#the-`withgroup`-method) + 1. [The `Handle` method](#the-`handle`-method) +1. [General considerations](#general-considerations) + 1. [Concurrency safety](#concurrency-safety) + 1. [Robustness](#robustness) + 1. [Speed](#speed) + + +# Introduction + +The standard library’s `log/slog` package has a two-part design. +A "frontend," implemented by the `Logger` type, +gathers stuctured log information like a message, level, and attributes, +and passes them to a "backend," an implementation of the `Handler` interface. +The package comes with two built-in handlers that usually should be adequate. +But you may need to write your own handler, and that is not always straightforward. +This guide is here to help. + + +# Loggers and their handlers + +Writing a handler requires an understanding of how the `Logger` and `Handler` +types work together. + +Each logger contains a handler. Certain `Logger` methods do some preliminary work, +such as gathering key-value pairs into `Attr`s, and then call one or more +`Handler` methods. These `Logger` methods are `With`, `WithGroup`, +and the output methods. + +An output method fulfills the main role of a logger: producing log output. +Here is an example call to an output method: + + logger.Info("hello", "key", value) + +There are two general output methods, `Log`, and `LogAttrs`. For convenience, +there is an output method for each of four common levels (`Debug`, `Info`, +`Warn` and `Error`), and corresponding methods that take a context (`DebugContext`, +`InfoContext`, `WarnContext` and `ErrorContext`). + +Each `Logger` output method first calls its handler's `Enabled` method. If that call +returns true, the method constructs a `Record` from its arguments and calls +the handler's `Handle` method. + +As a convenience and an optimization, attributes can be added to +`Logger` by calling the `With` method: + + logger = logger.With("k", v) + +This call creates a new `Logger` value with the argument attributes; the +original remains unchanged. +All subsequent output from `logger` will include those attributes. +A logger's `With` method calls its handler's `WithAttrs` method. + +The `WithGroup` method is used to avoid avoid key collisions in large programs +by establishing separate namespaces. + +This call creates a new `Logger` value with a group named "g": + + logger = logger.WithGroup("g") + +All subsequent keys for `logger` will be qualified by the group name "g". +Exactly what "qualified" means depends on how the logger's handler formats the +output. +The built-in `TextHandler` treats the group as a prefix to the key, separated by +a dot: `g.k` for a key `k`, for example. +The built-in `JSONHandler` uses the group as a key for a nested JSON object: + + {"g": {"k": v}} + +A logger's `WithGroup` method calls its handler's `WithGroup` method. + + +# Implementing `Handler` methods + +We can now talk about the four `Handler` methods in detail. +Along the way, we will write a handler that formats logs in YAML. +It will display this log output call: + + logger.Info("hello", "key", 23) + +as this YAML document: + + time: 2023-05-15T16:29:00 + level: INFO + message: hello + key: 23 + +## The `Enabled` method + +The `Enabled` method is an optimization that can avoid unnecessary work. +A `Logger` output method will call `Enabled` before it processes any of its arguments, +to see if it should proceed. + +The signature is + + Enabled(context.Context, Level) bool + +The context is available to allow decisions based on contextual information. +For example, a custom HTTP request header could specify a minimum level, +which the server adds to the context used for processing that request. +A handler's `Enabled` method could report whether the argument level +is greater than or equal to the context value, allowing the verbosity +of the work done by each request to be controlled independently. + +Most implementations of `Enabled` will consult a configured minimum level +instead. For maximum generality, use the `Leveler` type in the configuration of +your handler, as the built-in `HandlerOptions` does. + +Our YAML handler's constructor will take a `Leveler`, along with an `io.Writer` +for its output: + +TODO(jba): include func yamlhandler.New(w io.Writer, level slog.Leveler) + +`Leveler` is implemented by both `Level` and `LevelVar`. +A `Level` value is easy for the user to provide, +but changing the level of multiple handlers requires tracking them all. +If the user instead passes a `LevelVar`, then a single change to that `LevelVar` +will change the behavior of all handlers that contain it. +Changes to `LevelVar`s are goroutine-safe. + +TODO(jba): example handler that implements a minimum level and delegates the other methods. + +## The `WithAttrs` method + +One of `slog`'s performance optimizations is support for pre-formatting +attributes. The `Logger.With` method converts key-value pairs into `Attr`s and +then calls `Handler.WithAttrs`. +The handler may store the attributes for later consumption by the `Handle` method, +or it may take the opportunity to format the attributes now, once, +rather than doing so repeatedly on each call to `Handle`. + +The signature of the `WithAttrs` method is + + WithAttrs(attrs []Attr) Handler + +The attributes are the processed key-value pairs passed to `Logger.With`. +The return value should be a new instance of your handler that contains +the attributes, possibly pre-formatted. + +`WithAttrs` must return a new handler with the additional attributes, leaving +the original handler (its receiver) unchanged. For example, this call: + + logger2 := logger1.With("k", v) + +creates a new logger, `logger2`, with an additional attribute, but has no +effect on `logger1`. + + +We will show an example implementation of `WithAttrs` below, when we discuss `WithGroup`. + +## The `WithGroup` method + +`Logger.WithGroup` calls `Handler.WithGroup` directly, with the same +argument, the group name. +A handler should remember the name so it can use it to qualify all subsequent +attributes. + +The signature of `WithGroup` is: + + WithGroup(name string) Handler + +Like `WithAttrs`, the `WithGroup` method should return a new handler, not modify +the receiver. + +The implementations of `WithGroup` and `WithAttrs` are intertwined. +Consider this statement: + + logger = logger.WithGroup("g1").With("k1", 1).WithGroup("g2").With("k2", 2) + +Subsequent `logger` output should qualify key "k1" with group "g1", +and key "k2" with groups "g1" and "g2". +The order of the `Logger.WithGroup` and `Logger.With` calls must be respected by +the implementations of `Handler.WithGroup` and `Handler.WithAttrs`. + +We will look at two implementations of `WithGroup` and `WithAttrs`, one that pre-formats and +one that doesn't. + +TODO(jba): add YAML handler examples + +## The `Handle` method + +The `Handle` method is passed a `Record` containing all the information to be +logged for a single call to a `Logger` output method. +The `Handle` method should deal with it in some way. +One way is to output the `Record` in some format, as `TextHandler` and `JSONHandler` do. +But other options are to modify the `Record` and pass it on to another handler, +enqueue the `Record` for later processing, or ignore it. + +The signature of `Handle` is + + Handle(context.Context, Record) error + +The context is provided to support applications that provide logging information +along the call chain. In a break with usual Go practice, the `Handle` method +should not treat a canceled context as a signal to stop work. + +If `Handle` processes its `Record`, it should follow the rules in the +[documentation](https://pkg.go.dev/log/slog#Handler.Handle). +For example, a zero `Time` field should be ignored, as should zero `Attr`s. +To verify that your handler follows these rules and generally produces proper +output, use the [testing/slogtest package](https://pkg.go.dev/log/slog). + +Before processing the attributes in the `Record`, remember to process the +attributes from `WithAttrs` calls, qualified by the groups from `WithGroup` +calls. Then use `Record.Attrs` to process the attributes in the `Record`, also +qualified by the groups from `WithGroup` calls. + +The attributes provided to `Handler.WithAttrs` appear in the order the user passed them +to `Logger.With`, and `Record.Attrs` traverses attributes in the order the user +passed them to the `Logger` output method. Handlers are free to reorder or +de-duplicate the attributes, provided group qualification is preserved. + +Your handler can make a single copy of a `Record` with an ordinary Go +assignment, channel send or function call if it doesn't retain the +original. +But if its actions result in more than one copy, it should call `Record.Clone` +to make the copies so that they don't share state. +This `Handle` method passes the record to a single handler, so it doesn't require `Clone`: + + type Handler1 struct { + h slog.Handler + // ... + } + + func (h *Handler1) Handle(ctx context.Context, r slog.Record) error { + return h.h.Handle(ctx, r) + } + +This `Handle` method might pass the record to more than one handler, so it +should use `Clone`: + + type Handler2 struct { + hs []slog.Handler + // ... + } + + func (h *Handler2) Handle(ctx context.Context, r slog.Record) error { + for _, hh := range h.hs { + if err := hh.Handle(ctx, r.Clone()); err != nil { + return err + } + } + return nil + } + + + +TODO(jba): example use of slogtest + + +# General considerations + +## Concurrency safety + +A handler must work properly when a single `Logger` is shared among several +goroutines. +That means that mutable state must be protected with a lock or some other mechanism. +In practice, this is not hard to achieve, because many handlers won't have any +mutable state. + +- The `Enabled` method typically consults only its arguments and a configured + level. The level is often either set once initially, or is held in a + `LevelVar`, which is already concurrency-safe. + +- The `WithAttrs` and `WithGroup` methods should not modify the receiver, + for reasons discussed above. + +- The `Handle` method typically works only with its arguments and stored fields. + +Calls to output methods like `io.Writer.Write` should be synchronized unless +it can be verified that no locking is needed. Beware of facile claims like +"Unix writes are atomic"; the situation is a lot more nuanced than that. + +Some handlers have legitimate reasons for keeping state. +For example, a handler might support a `SetLevel` method to change its configured level +dynamically. +Or it might output the time between sucessive calls to `Handle`, +which requires a mutable field holding the last output time. +Synchronize all accesses to such fields, both reads and writes. + +The built-in handlers have no directly mutable state. +They use a mutex only to sequence calls to their contained `io.Writer`. + +## Robustness + +Logging is often the debugging technique of last resort. When it is difficult or +impossible to inspect a system, as is typically the case with a production +server, logs provide the most detailed way to understand its behavior. +Therefore, your handler should be robust to bad input. + +For example, the usual advice when when a function discovers a problem, +like an invalid argument, is to panic or return an error. +The built-in handlers do not follow that advice. +Few things are more frustrating than being unable to debug a problem that +causes logging to fail; +our feeling is that it is +better to produce some output, however imperfect, than to produce none at all. +That is why methods like `Logger.Info` convert programming bugs in their list of +key-value pairs, like missing values or malformed keys, +into `Attr`s that contain as much information as possible. + +One place to avoid panics is in processing attribute values. A handler that wants +to format a value will probably switch on the value's kind: + + switch attr.Value.Kind() { + case KindString: ... + case KindTime: ... + // all other Kinds + default: ... + } + +What should happen in the default case, when the handler encounters a `Kind` +that it doesn't know about? +The built-in handlers try to muddle through by using the result of the value's +`String` method. +They do not panic or return an error. +Your own handlers might in addition want to report the problem through your production monitoring +or error-tracking telemetry system. +The most likely explanation for the issue is that a newer version of the `slog` package added +a new `Kind`—a backwards-compatible change under the Go 1 Compatibility +Promise—and the handler wasn't updated. +That is certainly a problem, but it shouldn't deprive +readers of the logs from seeing the rest of the output. + +There is one circumstance where returning an error from `Handler.Handle` is appropriate. +If the output operation itself fails, the best course of action is to report +this failure by returning the error. For instance, the last two lines of the +built-in `Handle` methods are + + _, err := h.w.Write(*state.buf) + return err + +Although the output methods of `Logger` ignore the error, one could write a +handler that does something with it, perhaps falling back to writing to standard +error. + +## Speed + +TODO(jba): discuss diff --git a/slog-handler-guide/guide.md b/slog-handler-guide/guide.md new file mode 100644 index 00000000..dc4019ff --- /dev/null +++ b/slog-handler-guide/guide.md @@ -0,0 +1,342 @@ + +# A Guide to Writing `slog` Handlers + +This document is maintained by Jonathan Amsterdam `jba@google.com`. + + +# Contents + +%toc + + +# Introduction + +The standard library’s `log/slog` package has a two-part design. +A "frontend," implemented by the `Logger` type, +gathers stuctured log information like a message, level, and attributes, +and passes them to a "backend," an implementation of the `Handler` interface. +The package comes with two built-in handlers that usually should be adequate. +But you may need to write your own handler, and that is not always straightforward. +This guide is here to help. + + +# Loggers and their handlers + +Writing a handler requires an understanding of how the `Logger` and `Handler` +types work together. + +Each logger contains a handler. Certain `Logger` methods do some preliminary work, +such as gathering key-value pairs into `Attr`s, and then call one or more +`Handler` methods. These `Logger` methods are `With`, `WithGroup`, +and the output methods. + +An output method fulfills the main role of a logger: producing log output. +Here is an example call to an output method: + + logger.Info("hello", "key", value) + +There are two general output methods, `Log`, and `LogAttrs`. For convenience, +there is an output method for each of four common levels (`Debug`, `Info`, +`Warn` and `Error`), and corresponding methods that take a context (`DebugContext`, +`InfoContext`, `WarnContext` and `ErrorContext`). + +Each `Logger` output method first calls its handler's `Enabled` method. If that call +returns true, the method constructs a `Record` from its arguments and calls +the handler's `Handle` method. + +As a convenience and an optimization, attributes can be added to +`Logger` by calling the `With` method: + + logger = logger.With("k", v) + +This call creates a new `Logger` value with the argument attributes; the +original remains unchanged. +All subsequent output from `logger` will include those attributes. +A logger's `With` method calls its handler's `WithAttrs` method. + +The `WithGroup` method is used to avoid avoid key collisions in large programs +by establishing separate namespaces. + +This call creates a new `Logger` value with a group named "g": + + logger = logger.WithGroup("g") + +All subsequent keys for `logger` will be qualified by the group name "g". +Exactly what "qualified" means depends on how the logger's handler formats the +output. +The built-in `TextHandler` treats the group as a prefix to the key, separated by +a dot: `g.k` for a key `k`, for example. +The built-in `JSONHandler` uses the group as a key for a nested JSON object: + + {"g": {"k": v}} + +A logger's `WithGroup` method calls its handler's `WithGroup` method. + + +# Implementing `Handler` methods + +We can now talk about the four `Handler` methods in detail. +Along the way, we will write a handler that formats logs in YAML. +It will display this log output call: + + logger.Info("hello", "key", 23) + +as this YAML document: + + time: 2023-05-15T16:29:00 + level: INFO + message: hello + key: 23 + +## The `Enabled` method + +The `Enabled` method is an optimization that can avoid unnecessary work. +A `Logger` output method will call `Enabled` before it processes any of its arguments, +to see if it should proceed. + +The signature is + + Enabled(context.Context, Level) bool + +The context is available to allow decisions based on contextual information. +For example, a custom HTTP request header could specify a minimum level, +which the server adds to the context used for processing that request. +A handler's `Enabled` method could report whether the argument level +is greater than or equal to the context value, allowing the verbosity +of the work done by each request to be controlled independently. + +Most implementations of `Enabled` will consult a configured minimum level +instead. For maximum generality, use the `Leveler` type in the configuration of +your handler, as the built-in `HandlerOptions` does. + +Our YAML handler's constructor will take a `Leveler`, along with an `io.Writer` +for its output: + +TODO(jba): include func yamlhandler.New(w io.Writer, level slog.Leveler) + +`Leveler` is implemented by both `Level` and `LevelVar`. +A `Level` value is easy for the user to provide, +but changing the level of multiple handlers requires tracking them all. +If the user instead passes a `LevelVar`, then a single change to that `LevelVar` +will change the behavior of all handlers that contain it. +Changes to `LevelVar`s are goroutine-safe. + +TODO(jba): example handler that implements a minimum level and delegates the other methods. + +## The `WithAttrs` method + +One of `slog`'s performance optimizations is support for pre-formatting +attributes. The `Logger.With` method converts key-value pairs into `Attr`s and +then calls `Handler.WithAttrs`. +The handler may store the attributes for later consumption by the `Handle` method, +or it may take the opportunity to format the attributes now, once, +rather than doing so repeatedly on each call to `Handle`. + +The signature of the `WithAttrs` method is + + WithAttrs(attrs []Attr) Handler + +The attributes are the processed key-value pairs passed to `Logger.With`. +The return value should be a new instance of your handler that contains +the attributes, possibly pre-formatted. + +`WithAttrs` must return a new handler with the additional attributes, leaving +the original handler (its receiver) unchanged. For example, this call: + + logger2 := logger1.With("k", v) + +creates a new logger, `logger2`, with an additional attribute, but has no +effect on `logger1`. + + +We will show an example implementation of `WithAttrs` below, when we discuss `WithGroup`. + +## The `WithGroup` method + +`Logger.WithGroup` calls `Handler.WithGroup` directly, with the same +argument, the group name. +A handler should remember the name so it can use it to qualify all subsequent +attributes. + +The signature of `WithGroup` is: + + WithGroup(name string) Handler + +Like `WithAttrs`, the `WithGroup` method should return a new handler, not modify +the receiver. + +The implementations of `WithGroup` and `WithAttrs` are intertwined. +Consider this statement: + + logger = logger.WithGroup("g1").With("k1", 1).WithGroup("g2").With("k2", 2) + +Subsequent `logger` output should qualify key "k1" with group "g1", +and key "k2" with groups "g1" and "g2". +The order of the `Logger.WithGroup` and `Logger.With` calls must be respected by +the implementations of `Handler.WithGroup` and `Handler.WithAttrs`. + +We will look at two implementations of `WithGroup` and `WithAttrs`, one that pre-formats and +one that doesn't. + +TODO(jba): add YAML handler examples + +## The `Handle` method + +The `Handle` method is passed a `Record` containing all the information to be +logged for a single call to a `Logger` output method. +The `Handle` method should deal with it in some way. +One way is to output the `Record` in some format, as `TextHandler` and `JSONHandler` do. +But other options are to modify the `Record` and pass it on to another handler, +enqueue the `Record` for later processing, or ignore it. + +The signature of `Handle` is + + Handle(context.Context, Record) error + +The context is provided to support applications that provide logging information +along the call chain. In a break with usual Go practice, the `Handle` method +should not treat a canceled context as a signal to stop work. + +If `Handle` processes its `Record`, it should follow the rules in the +[documentation](https://pkg.go.dev/log/slog#Handler.Handle). +For example, a zero `Time` field should be ignored, as should zero `Attr`s. +To verify that your handler follows these rules and generally produces proper +output, use the [testing/slogtest package](https://pkg.go.dev/log/slog). + +Before processing the attributes in the `Record`, remember to process the +attributes from `WithAttrs` calls, qualified by the groups from `WithGroup` +calls. Then use `Record.Attrs` to process the attributes in the `Record`, also +qualified by the groups from `WithGroup` calls. + +The attributes provided to `Handler.WithAttrs` appear in the order the user passed them +to `Logger.With`, and `Record.Attrs` traverses attributes in the order the user +passed them to the `Logger` output method. Handlers are free to reorder or +de-duplicate the attributes, provided group qualification is preserved. + +Your handler can make a single copy of a `Record` with an ordinary Go +assignment, channel send or function call if it doesn't retain the +original. +But if its actions result in more than one copy, it should call `Record.Clone` +to make the copies so that they don't share state. +This `Handle` method passes the record to a single handler, so it doesn't require `Clone`: + + type Handler1 struct { + h slog.Handler + // ... + } + + func (h *Handler1) Handle(ctx context.Context, r slog.Record) error { + return h.h.Handle(ctx, r) + } + +This `Handle` method might pass the record to more than one handler, so it +should use `Clone`: + + type Handler2 struct { + hs []slog.Handler + // ... + } + + func (h *Handler2) Handle(ctx context.Context, r slog.Record) error { + for _, hh := range h.hs { + if err := hh.Handle(ctx, r.Clone()); err != nil { + return err + } + } + return nil + } + + + +TODO(jba): example use of slogtest + + +# General considerations + +## Concurrency safety + +A handler must work properly when a single `Logger` is shared among several +goroutines. +That means that mutable state must be protected with a lock or some other mechanism. +In practice, this is not hard to achieve, because many handlers won't have any +mutable state. + +- The `Enabled` method typically consults only its arguments and a configured + level. The level is often either set once initially, or is held in a + `LevelVar`, which is already concurrency-safe. + +- The `WithAttrs` and `WithGroup` methods should not modify the receiver, + for reasons discussed above. + +- The `Handle` method typically works only with its arguments and stored fields. + +Calls to output methods like `io.Writer.Write` should be synchronized unless +it can be verified that no locking is needed. Beware of facile claims like +"Unix writes are atomic"; the situation is a lot more nuanced than that. + +Some handlers have legitimate reasons for keeping state. +For example, a handler might support a `SetLevel` method to change its configured level +dynamically. +Or it might output the time between sucessive calls to `Handle`, +which requires a mutable field holding the last output time. +Synchronize all accesses to such fields, both reads and writes. + +The built-in handlers have no directly mutable state. +They use a mutex only to sequence calls to their contained `io.Writer`. + +## Robustness + +Logging is often the debugging technique of last resort. When it is difficult or +impossible to inspect a system, as is typically the case with a production +server, logs provide the most detailed way to understand its behavior. +Therefore, your handler should be robust to bad input. + +For example, the usual advice when when a function discovers a problem, +like an invalid argument, is to panic or return an error. +The built-in handlers do not follow that advice. +Few things are more frustrating than being unable to debug a problem that +causes logging to fail; +our feeling is that it is +better to produce some output, however imperfect, than to produce none at all. +That is why methods like `Logger.Info` convert programming bugs in their list of +key-value pairs, like missing values or malformed keys, +into `Attr`s that contain as much information as possible. + +One place to avoid panics is in processing attribute values. A handler that wants +to format a value will probably switch on the value's kind: + + switch attr.Value.Kind() { + case KindString: ... + case KindTime: ... + // all other Kinds + default: ... + } + +What should happen in the default case, when the handler encounters a `Kind` +that it doesn't know about? +The built-in handlers try to muddle through by using the result of the value's +`String` method. +They do not panic or return an error. +Your own handlers might in addition want to report the problem through your production monitoring +or error-tracking telemetry system. +The most likely explanation for the issue is that a newer version of the `slog` package added +a new `Kind`—a backwards-compatible change under the Go 1 Compatibility +Promise—and the handler wasn't updated. +That is certainly a problem, but it shouldn't deprive +readers of the logs from seeing the rest of the output. + +There is one circumstance where returning an error from `Handler.Handle` is appropriate. +If the output operation itself fails, the best course of action is to report +this failure by returning the error. For instance, the last two lines of the +built-in `Handle` methods are + + _, err := h.w.Write(*state.buf) + return err + +Although the output methods of `Logger` ignore the error, one could write a +handler that does something with it, perhaps falling back to writing to standard +error. + +## Speed + +TODO(jba): discuss From 7137c6b752c6c86b576aebe442a82f28698f916f Mon Sep 17 00:00:00 2001 From: Jonathan Amsterdam Date: Fri, 14 Jul 2023 16:52:32 -0400 Subject: [PATCH 08/48] slog-handler-guide: handler example: types Begin discussing a running example of a slog.Handler implementation. Initially we'll ignore the WithAttrs and WithGroup methods. The implementation that does that is in indenthandler1. This CL contains the complete implementation, but discusses only the types and constructor function. Change-Id: I3b635aee66a6d5df64bc13ce6bfb7ae4881606fe Reviewed-on: https://go-review.googlesource.com/c/example/+/509955 Reviewed-by: Ian Cottrell TryBot-Result: Gopher Robot Run-TryBot: Jonathan Amsterdam --- slog-handler-guide/README.md | 85 +++++++++---- slog-handler-guide/guide.md | 60 +++++---- .../indenthandler1/indent_handler.go | 117 ++++++++++++++++++ .../indenthandler1/indent_handler_test.go | 40 ++++++ 4 files changed, 258 insertions(+), 44 deletions(-) create mode 100644 slog-handler-guide/indenthandler1/indent_handler.go create mode 100644 slog-handler-guide/indenthandler1/indent_handler_test.go diff --git a/slog-handler-guide/README.md b/slog-handler-guide/README.md index 7245e7e9..6e501b9d 100644 --- a/slog-handler-guide/README.md +++ b/slog-handler-guide/README.md @@ -87,17 +87,74 @@ A logger's `WithGroup` method calls its handler's `WithGroup` method. # Implementing `Handler` methods We can now talk about the four `Handler` methods in detail. -Along the way, we will write a handler that formats logs in YAML. -It will display this log output call: +Along the way, we will write a handler that formats logs using a format +reminsicent of YAML. It will display this log output call: logger.Info("hello", "key", 23) -as this YAML document: +something like this: time: 2023-05-15T16:29:00 level: INFO - message: hello + message: "hello" key: 23 + --- + +Although this particular output is valid YAML, +our implementation doesn't consider the subtleties of YAML syntax, +so it will sometimes produce invalid YAML. +For example, it doesn't quote keys that have colons in them. +We'll call it `IndentHandler` to forestall disappointment. + +We begin with the `IndentHandler` type +and the `New` function that constructs it from an `io.Writer` and options: + +``` +type IndentHandler struct { + opts Options + // TODO: state for WithGroup and WithAttrs + mu *sync.Mutex + out io.Writer +} + +type Options struct { + // Level reports the minimum level to log. + // Levels with lower levels are discarded. + // If nil, the Handler uses [slog.LevelInfo]. + Level slog.Leveler +} + +func New(out io.Writer, opts *Options) *IndentHandler { + h := &IndentHandler{out: out, mu: &sync.Mutex{}} + if opts != nil { + h.opts = *opts + } + if h.opts.Level == nil { + h.opts.Level = slog.LevelInfo + } + return h +} +``` + +We'll support only one option, the ability to set a minimum level in order to +supress detailed log output. +Handlers should always use the `slog.Leveler` type for this option. +`Leveler` is implemented by both `Level` and `LevelVar`. +A `Level` value is easy for the user to provide, +but changing the level of multiple handlers requires tracking them all. +If the user instead passes a `LevelVar`, then a single change to that `LevelVar` +will change the behavior of all handlers that contain it. +Changes to `LevelVar`s are goroutine-safe. + +The mutex will be used to ensure that writes to the `io.Writer` happen atomically. +Unusually, `IndentHandler` holds a pointer to a `sync.Mutex` rather than holding a +`sync.Mutex` directly. +But there is a good reason for that, which we'll explain later. + +TODO(jba): add link to that later explanation. + +Our handler will need additional state to track calls to `WithGroup` and `WithAttrs`. +We will describe that state when we get to those methods. ## The `Enabled` method @@ -116,23 +173,7 @@ A handler's `Enabled` method could report whether the argument level is greater than or equal to the context value, allowing the verbosity of the work done by each request to be controlled independently. -Most implementations of `Enabled` will consult a configured minimum level -instead. For maximum generality, use the `Leveler` type in the configuration of -your handler, as the built-in `HandlerOptions` does. - -Our YAML handler's constructor will take a `Leveler`, along with an `io.Writer` -for its output: - -TODO(jba): include func yamlhandler.New(w io.Writer, level slog.Leveler) - -`Leveler` is implemented by both `Level` and `LevelVar`. -A `Level` value is easy for the user to provide, -but changing the level of multiple handlers requires tracking them all. -If the user instead passes a `LevelVar`, then a single change to that `LevelVar` -will change the behavior of all handlers that contain it. -Changes to `LevelVar`s are goroutine-safe. - -TODO(jba): example handler that implements a minimum level and delegates the other methods. +TODO(jba): include Enabled example ## The `WithAttrs` method @@ -189,7 +230,7 @@ the implementations of `Handler.WithGroup` and `Handler.WithAttrs`. We will look at two implementations of `WithGroup` and `WithAttrs`, one that pre-formats and one that doesn't. -TODO(jba): add YAML handler examples +TODO(jba): add IndentHandler examples ## The `Handle` method diff --git a/slog-handler-guide/guide.md b/slog-handler-guide/guide.md index dc4019ff..e780016e 100644 --- a/slog-handler-guide/guide.md +++ b/slog-handler-guide/guide.md @@ -76,17 +76,49 @@ A logger's `WithGroup` method calls its handler's `WithGroup` method. # Implementing `Handler` methods We can now talk about the four `Handler` methods in detail. -Along the way, we will write a handler that formats logs in YAML. -It will display this log output call: +Along the way, we will write a handler that formats logs using a format +reminsicent of YAML. It will display this log output call: logger.Info("hello", "key", 23) -as this YAML document: +something like this: time: 2023-05-15T16:29:00 level: INFO - message: hello + message: "hello" key: 23 + --- + +Although this particular output is valid YAML, +our implementation doesn't consider the subtleties of YAML syntax, +so it will sometimes produce invalid YAML. +For example, it doesn't quote keys that have colons in them. +We'll call it `IndentHandler` to forestall disappointment. + +We begin with the `IndentHandler` type +and the `New` function that constructs it from an `io.Writer` and options: + +%include indenthandler1/indent_handler.go types - + +We'll support only one option, the ability to set a minimum level in order to +supress detailed log output. +Handlers should always use the `slog.Leveler` type for this option. +`Leveler` is implemented by both `Level` and `LevelVar`. +A `Level` value is easy for the user to provide, +but changing the level of multiple handlers requires tracking them all. +If the user instead passes a `LevelVar`, then a single change to that `LevelVar` +will change the behavior of all handlers that contain it. +Changes to `LevelVar`s are goroutine-safe. + +The mutex will be used to ensure that writes to the `io.Writer` happen atomically. +Unusually, `IndentHandler` holds a pointer to a `sync.Mutex` rather than holding a +`sync.Mutex` directly. +But there is a good reason for that, which we'll explain later. + +TODO(jba): add link to that later explanation. + +Our handler will need additional state to track calls to `WithGroup` and `WithAttrs`. +We will describe that state when we get to those methods. ## The `Enabled` method @@ -105,23 +137,7 @@ A handler's `Enabled` method could report whether the argument level is greater than or equal to the context value, allowing the verbosity of the work done by each request to be controlled independently. -Most implementations of `Enabled` will consult a configured minimum level -instead. For maximum generality, use the `Leveler` type in the configuration of -your handler, as the built-in `HandlerOptions` does. - -Our YAML handler's constructor will take a `Leveler`, along with an `io.Writer` -for its output: - -TODO(jba): include func yamlhandler.New(w io.Writer, level slog.Leveler) - -`Leveler` is implemented by both `Level` and `LevelVar`. -A `Level` value is easy for the user to provide, -but changing the level of multiple handlers requires tracking them all. -If the user instead passes a `LevelVar`, then a single change to that `LevelVar` -will change the behavior of all handlers that contain it. -Changes to `LevelVar`s are goroutine-safe. - -TODO(jba): example handler that implements a minimum level and delegates the other methods. +TODO(jba): include Enabled example ## The `WithAttrs` method @@ -178,7 +194,7 @@ the implementations of `Handler.WithGroup` and `Handler.WithAttrs`. We will look at two implementations of `WithGroup` and `WithAttrs`, one that pre-formats and one that doesn't. -TODO(jba): add YAML handler examples +TODO(jba): add IndentHandler examples ## The `Handle` method diff --git a/slog-handler-guide/indenthandler1/indent_handler.go b/slog-handler-guide/indenthandler1/indent_handler.go new file mode 100644 index 00000000..41f1a067 --- /dev/null +++ b/slog-handler-guide/indenthandler1/indent_handler.go @@ -0,0 +1,117 @@ +//go:build go1.21 + +package indenthandler + +import ( + "context" + "fmt" + "io" + "log/slog" + "runtime" + "sync" + "time" +) + +// !+types +type IndentHandler struct { + opts Options + // TODO: state for WithGroup and WithAttrs + mu *sync.Mutex + out io.Writer +} + +type Options struct { + // Level reports the minimum level to log. + // Levels with lower levels are discarded. + // If nil, the Handler uses [slog.LevelInfo]. + Level slog.Leveler +} + +func New(out io.Writer, opts *Options) *IndentHandler { + h := &IndentHandler{out: out, mu: &sync.Mutex{}} + if opts != nil { + h.opts = *opts + } + if h.opts.Level == nil { + h.opts.Level = slog.LevelInfo + } + return h +} + +//!-types + +func (h *IndentHandler) Enabled(ctx context.Context, level slog.Level) bool { + return level >= h.opts.Level.Level() +} + +func (h *IndentHandler) WithGroup(name string) slog.Handler { + // TODO: implement. + return h +} + +func (h *IndentHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + // TODO: implement. + return h +} + +func (h *IndentHandler) Handle(ctx context.Context, r slog.Record) error { + buf := make([]byte, 0, 1024) + if !r.Time.IsZero() { + buf = h.appendAttr(buf, slog.Time(slog.TimeKey, r.Time), 0) + } + buf = h.appendAttr(buf, slog.Any(slog.LevelKey, r.Level), 0) + if r.PC != 0 { + fs := runtime.CallersFrames([]uintptr{r.PC}) + f, _ := fs.Next() + buf = h.appendAttr(buf, slog.String(slog.SourceKey, fmt.Sprintf("%s:%d", f.File, f.Line)), 0) + } + buf = h.appendAttr(buf, slog.String(slog.MessageKey, r.Message), 0) + indentLevel := 0 + // TODO: output the Attrs and groups from WithAttrs and WithGroup. + r.Attrs(func(a slog.Attr) bool { + buf = h.appendAttr(buf, a, indentLevel) + return true + }) + buf = append(buf, "---\n"...) + h.mu.Lock() + defer h.mu.Unlock() + _, err := h.out.Write(buf) + return err +} + +func (h *IndentHandler) appendAttr(buf []byte, a slog.Attr, indentLevel int) []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 + } + // Indent 4 spaces per level. + buf = fmt.Appendf(buf, "%*s", indentLevel*4, "") + switch a.Value.Kind() { + case slog.KindString: + // Quote string values, to make them easy to parse. + buf = fmt.Appendf(buf, "%s: %q\n", a.Key, a.Value.String()) + case slog.KindTime: + // Write times in a standard way, without the monotonic time. + buf = fmt.Appendf(buf, "%s: %s\n", a.Key, a.Value.Time().Format(time.RFC3339Nano)) + case slog.KindGroup: + attrs := a.Value.Group() + // Ignore empty groups. + if len(attrs) == 0 { + return buf + } + // If the key is non-empty, write it out and indent the rest of the attrs. + // Otherwise, inline the attrs. + if a.Key != "" { + buf = fmt.Appendf(buf, "%s:\n", a.Key) + indentLevel++ + } + for _, ga := range attrs { + buf = h.appendAttr(buf, ga, indentLevel) + } + default: + buf = fmt.Appendf(buf, "%s: %s\n", a.Key, a.Value) + } + return buf +} diff --git a/slog-handler-guide/indenthandler1/indent_handler_test.go b/slog-handler-guide/indenthandler1/indent_handler_test.go new file mode 100644 index 00000000..b295fa66 --- /dev/null +++ b/slog-handler-guide/indenthandler1/indent_handler_test.go @@ -0,0 +1,40 @@ +//go:build go1.21 + +package indenthandler + +import ( + "bytes" + "regexp" + "testing" + + "log/slog" +) + +func Test(t *testing.T) { + var buf bytes.Buffer + l := slog.New(New(&buf, nil)) + l.Info("hello", "a", 1, "b", true, "c", 3.14, slog.Group("g", "h", 1, "i", 2), "d", "NO") + got := buf.String() + wantre := `time: [-0-9T:.]+Z? +level: INFO +source: ".*/indent_handler_test.go:\d+" +msg: "hello" +a: 1 +b: true +c: 3.14 +g: + h: 1 + i: 2 +d: "NO" +` + re := regexp.MustCompile(wantre) + if !re.MatchString(got) { + t.Errorf("\ngot:\n%q\nwant:\n%q", got, wantre) + } + + buf.Reset() + l.Debug("test") + if got := buf.Len(); got != 0 { + t.Errorf("got buf.Len() = %d, want 0", got) + } +} From 29ffb748dd554bd00e219ab81e800bb416401b83 Mon Sep 17 00:00:00 2001 From: Jonathan Amsterdam Date: Fri, 14 Jul 2023 20:52:58 -0400 Subject: [PATCH 09/48] slog-handler-guide: handler example: Handle method Pull the discussion of the Handle method up. It makes more sense to talk about it before WithGroup and WithAttrs. Change-Id: I01a1b4b98131079cf74d2d965742a48acf5c3407 Reviewed-on: https://go-review.googlesource.com/c/example/+/509957 TryBot-Result: Gopher Robot Reviewed-by: Ian Cottrell Run-TryBot: Jonathan Amsterdam --- slog-handler-guide/README.md | 170 +++++++++++++++++++++-------------- slog-handler-guide/guide.md | 142 +++++++++++++++-------------- 2 files changed, 179 insertions(+), 133 deletions(-) diff --git a/slog-handler-guide/README.md b/slog-handler-guide/README.md index 6e501b9d..45dd61ef 100644 --- a/slog-handler-guide/README.md +++ b/slog-handler-guide/README.md @@ -11,9 +11,10 @@ This document is maintained by Jonathan Amsterdam `jba@google.com`. 1. [Loggers and their handlers](#loggers-and-their-handlers) 1. [Implementing `Handler` methods](#implementing-`handler`-methods) 1. [The `Enabled` method](#the-`enabled`-method) + 1. [The `Handle` method](#the-`handle`-method) 1. [The `WithAttrs` method](#the-`withattrs`-method) 1. [The `WithGroup` method](#the-`withgroup`-method) - 1. [The `Handle` method](#the-`handle`-method) + 1. [Testing](#testing) 1. [General considerations](#general-considerations) 1. [Concurrency safety](#concurrency-safety) 1. [Robustness](#robustness) @@ -149,7 +150,7 @@ Changes to `LevelVar`s are goroutine-safe. The mutex will be used to ensure that writes to the `io.Writer` happen atomically. Unusually, `IndentHandler` holds a pointer to a `sync.Mutex` rather than holding a `sync.Mutex` directly. -But there is a good reason for that, which we'll explain later. +There is a good reason for that, which we'll explain later. TODO(jba): add link to that later explanation. @@ -175,6 +176,101 @@ of the work done by each request to be controlled independently. TODO(jba): include Enabled example +## The `Handle` method + +The `Handle` method is passed a `Record` containing all the information to be +logged for a single call to a `Logger` output method. +The `Handle` method should deal with it in some way. +One way is to output the `Record` in some format, as `TextHandler` and `JSONHandler` do. +But other options are to modify the `Record` and pass it on to another handler, +enqueue the `Record` for later processing, or ignore it. + +The signature of `Handle` is + + Handle(context.Context, Record) error + +The context is provided to support applications that provide logging information +along the call chain. In a break with usual Go practice, the `Handle` method +should not treat a canceled context as a signal to stop work. + +If `Handle` processes its `Record`, it should follow the rules in the +[documentation](https://pkg.go.dev/log/slog#Handler.Handle). +For example, a zero `Time` field should be ignored, as should zero `Attr`s. + +A `Handle` method that is going to produce output should carry out the following steps: + +1. Allocate a buffer, typically a `[]byte`, to hold the output. +It's best to construct the output in memory first, +then write it with a single call to `io.Writer.Write`, +to minimize interleaving with other goroutines using the same writer. + +2. Format the special fields: time, level, message, and source location (PC). +As a general rule, these fields should appear first and are not nested in +groups established by `WithGroup`. + +3. Format the result of `WithGroup` and `WithAttrs` calls. + +4. Format the attributes in the `Record`. + +5. Output the buffer. + +That is how our `IndentHandler`'s `Handle` method is structured: + +``` +func (h *IndentHandler) Handle(ctx context.Context, r slog.Record) error { + buf := make([]byte, 0, 1024) + if !r.Time.IsZero() { + buf = h.appendAttr(buf, slog.Time(slog.TimeKey, r.Time), 0) + } + buf = h.appendAttr(buf, slog.Any(slog.LevelKey, r.Level), 0) + if r.PC != 0 { + fs := runtime.CallersFrames([]uintptr{r.PC}) + f, _ := fs.Next() + buf = h.appendAttr(buf, slog.String(slog.SourceKey, fmt.Sprintf("%s:%d", f.File, f.Line)), 0) + } + buf = h.appendAttr(buf, slog.String(slog.MessageKey, r.Message), 0) + indentLevel := 0 + // TODO: output the Attrs and groups from WithAttrs and WithGroup. + r.Attrs(func(a slog.Attr) bool { + buf = h.appendAttr(buf, a, indentLevel) + return true + }) + buf = append(buf, "---\n"...) + h.mu.Lock() + defer h.mu.Unlock() + _, err := h.out.Write(buf) + return err +} +``` + +The first line allocates a `[]byte` that should be large enough for most log +output. +Allocating a buffer with some initial, fairly large capacity is a simple but +significant optimization: it avoids the repeated copying and allocation that +happen when the initial slice is empty or small. +We'll return to this line in the section on [speed](#speed) +and show how we can do even better. + +The next part of our `Handle` method formats the special attributes, +observing the rules to ignore a zero time and a zero PC. + +Next, the method processes the result of `WithAttrs` and `WithGroup` calls. +We'll skip that for now. + +Then it's time to process the attributes in the argument record. +We use the `Record.Attrs` method to iterate over the attributes +in the order the user passed them to the `Logger` output method. +Handlers are free to reorder or de-duplicate the attributes, +but ours does not. + +Lastly, after adding the line "---" to the output to separate log records, +our handler makes a single call to `h.out.Write` with the buffer we've accumulated. +We hold the lock for this write to make it atomic with respect to other +goroutines that may be calling `Handle` at the same time. + +TODO(jba): talk about appendAttr + + ## The `WithAttrs` method One of `slog`'s performance optimizations is support for pre-formatting @@ -232,76 +328,14 @@ one that doesn't. TODO(jba): add IndentHandler examples -## The `Handle` method +## Testing -The `Handle` method is passed a `Record` containing all the information to be -logged for a single call to a `Logger` output method. -The `Handle` method should deal with it in some way. -One way is to output the `Record` in some format, as `TextHandler` and `JSONHandler` do. -But other options are to modify the `Record` and pass it on to another handler, -enqueue the `Record` for later processing, or ignore it. - -The signature of `Handle` is - - Handle(context.Context, Record) error - -The context is provided to support applications that provide logging information -along the call chain. In a break with usual Go practice, the `Handle` method -should not treat a canceled context as a signal to stop work. - -If `Handle` processes its `Record`, it should follow the rules in the -[documentation](https://pkg.go.dev/log/slog#Handler.Handle). -For example, a zero `Time` field should be ignored, as should zero `Attr`s. To verify that your handler follows these rules and generally produces proper output, use the [testing/slogtest package](https://pkg.go.dev/log/slog). -Before processing the attributes in the `Record`, remember to process the -attributes from `WithAttrs` calls, qualified by the groups from `WithGroup` -calls. Then use `Record.Attrs` to process the attributes in the `Record`, also -qualified by the groups from `WithGroup` calls. - -The attributes provided to `Handler.WithAttrs` appear in the order the user passed them -to `Logger.With`, and `Record.Attrs` traverses attributes in the order the user -passed them to the `Logger` output method. Handlers are free to reorder or -de-duplicate the attributes, provided group qualification is preserved. - -Your handler can make a single copy of a `Record` with an ordinary Go -assignment, channel send or function call if it doesn't retain the -original. -But if its actions result in more than one copy, it should call `Record.Clone` -to make the copies so that they don't share state. -This `Handle` method passes the record to a single handler, so it doesn't require `Clone`: - - type Handler1 struct { - h slog.Handler - // ... - } - - func (h *Handler1) Handle(ctx context.Context, r slog.Record) error { - return h.h.Handle(ctx, r) - } - -This `Handle` method might pass the record to more than one handler, so it -should use `Clone`: - - type Handler2 struct { - hs []slog.Handler - // ... - } - - func (h *Handler2) Handle(ctx context.Context, r slog.Record) error { - for _, hh := range h.hs { - if err := hh.Handle(ctx, r.Clone()); err != nil { - return err - } - } - return nil - } - - - -TODO(jba): example use of slogtest +TODO(jba): show the test function. +TODO(jba): reintroduce the material on Record.Clone that used to be here. # General considerations @@ -392,3 +426,5 @@ error. ## Speed TODO(jba): discuss + +TODO(jba): show how to pool a []byte. diff --git a/slog-handler-guide/guide.md b/slog-handler-guide/guide.md index e780016e..683e367b 100644 --- a/slog-handler-guide/guide.md +++ b/slog-handler-guide/guide.md @@ -113,7 +113,7 @@ Changes to `LevelVar`s are goroutine-safe. The mutex will be used to ensure that writes to the `io.Writer` happen atomically. Unusually, `IndentHandler` holds a pointer to a `sync.Mutex` rather than holding a `sync.Mutex` directly. -But there is a good reason for that, which we'll explain later. +There is a good reason for that, which we'll explain later. TODO(jba): add link to that later explanation. @@ -139,6 +139,76 @@ of the work done by each request to be controlled independently. TODO(jba): include Enabled example +## The `Handle` method + +The `Handle` method is passed a `Record` containing all the information to be +logged for a single call to a `Logger` output method. +The `Handle` method should deal with it in some way. +One way is to output the `Record` in some format, as `TextHandler` and `JSONHandler` do. +But other options are to modify the `Record` and pass it on to another handler, +enqueue the `Record` for later processing, or ignore it. + +The signature of `Handle` is + + Handle(context.Context, Record) error + +The context is provided to support applications that provide logging information +along the call chain. In a break with usual Go practice, the `Handle` method +should not treat a canceled context as a signal to stop work. + +If `Handle` processes its `Record`, it should follow the rules in the +[documentation](https://pkg.go.dev/log/slog#Handler.Handle). +For example, a zero `Time` field should be ignored, as should zero `Attr`s. + +A `Handle` method that is going to produce output should carry out the following steps: + +1. Allocate a buffer, typically a `[]byte`, to hold the output. +It's best to construct the output in memory first, +then write it with a single call to `io.Writer.Write`, +to minimize interleaving with other goroutines using the same writer. + +2. Format the special fields: time, level, message, and source location (PC). +As a general rule, these fields should appear first and are not nested in +groups established by `WithGroup`. + +3. Format the result of `WithGroup` and `WithAttrs` calls. + +4. Format the attributes in the `Record`. + +5. Output the buffer. + +That is how our `IndentHandler`'s `Handle` method is structured: + +%include indenthandler1/indent_handler.go handle - + +The first line allocates a `[]byte` that should be large enough for most log +output. +Allocating a buffer with some initial, fairly large capacity is a simple but +significant optimization: it avoids the repeated copying and allocation that +happen when the initial slice is empty or small. +We'll return to this line in the section on [speed](#speed) +and show how we can do even better. + +The next part of our `Handle` method formats the special attributes, +observing the rules to ignore a zero time and a zero PC. + +Next, the method processes the result of `WithAttrs` and `WithGroup` calls. +We'll skip that for now. + +Then it's time to process the attributes in the argument record. +We use the `Record.Attrs` method to iterate over the attributes +in the order the user passed them to the `Logger` output method. +Handlers are free to reorder or de-duplicate the attributes, +but ours does not. + +Lastly, after adding the line "---" to the output to separate log records, +our handler makes a single call to `h.out.Write` with the buffer we've accumulated. +We hold the lock for this write to make it atomic with respect to other +goroutines that may be calling `Handle` at the same time. + +TODO(jba): talk about appendAttr + + ## The `WithAttrs` method One of `slog`'s performance optimizations is support for pre-formatting @@ -196,76 +266,14 @@ one that doesn't. TODO(jba): add IndentHandler examples -## The `Handle` method - -The `Handle` method is passed a `Record` containing all the information to be -logged for a single call to a `Logger` output method. -The `Handle` method should deal with it in some way. -One way is to output the `Record` in some format, as `TextHandler` and `JSONHandler` do. -But other options are to modify the `Record` and pass it on to another handler, -enqueue the `Record` for later processing, or ignore it. - -The signature of `Handle` is - - Handle(context.Context, Record) error - -The context is provided to support applications that provide logging information -along the call chain. In a break with usual Go practice, the `Handle` method -should not treat a canceled context as a signal to stop work. +## Testing -If `Handle` processes its `Record`, it should follow the rules in the -[documentation](https://pkg.go.dev/log/slog#Handler.Handle). -For example, a zero `Time` field should be ignored, as should zero `Attr`s. To verify that your handler follows these rules and generally produces proper output, use the [testing/slogtest package](https://pkg.go.dev/log/slog). -Before processing the attributes in the `Record`, remember to process the -attributes from `WithAttrs` calls, qualified by the groups from `WithGroup` -calls. Then use `Record.Attrs` to process the attributes in the `Record`, also -qualified by the groups from `WithGroup` calls. - -The attributes provided to `Handler.WithAttrs` appear in the order the user passed them -to `Logger.With`, and `Record.Attrs` traverses attributes in the order the user -passed them to the `Logger` output method. Handlers are free to reorder or -de-duplicate the attributes, provided group qualification is preserved. - -Your handler can make a single copy of a `Record` with an ordinary Go -assignment, channel send or function call if it doesn't retain the -original. -But if its actions result in more than one copy, it should call `Record.Clone` -to make the copies so that they don't share state. -This `Handle` method passes the record to a single handler, so it doesn't require `Clone`: - - type Handler1 struct { - h slog.Handler - // ... - } - - func (h *Handler1) Handle(ctx context.Context, r slog.Record) error { - return h.h.Handle(ctx, r) - } - -This `Handle` method might pass the record to more than one handler, so it -should use `Clone`: - - type Handler2 struct { - hs []slog.Handler - // ... - } - - func (h *Handler2) Handle(ctx context.Context, r slog.Record) error { - for _, hh := range h.hs { - if err := hh.Handle(ctx, r.Clone()); err != nil { - return err - } - } - return nil - } - - - -TODO(jba): example use of slogtest +TODO(jba): show the test function. +TODO(jba): reintroduce the material on Record.Clone that used to be here. # General considerations @@ -356,3 +364,5 @@ error. ## Speed TODO(jba): discuss + +TODO(jba): show how to pool a []byte. From f37d043b206a4aa588f59e05ecddc599a76bf9fa Mon Sep 17 00:00:00 2001 From: Jonathan Amsterdam Date: Thu, 20 Jul 2023 12:25:40 -0400 Subject: [PATCH 10/48] slog-handler-guide: appendAttrs method Change-Id: Ib2ba817b456ceb8c8b697d3ee24ba6c2884c942b Reviewed-on: https://go-review.googlesource.com/c/example/+/511635 Run-TryBot: Jonathan Amsterdam Reviewed-by: Ian Cottrell TryBot-Result: Gopher Robot --- slog-handler-guide/README.md | 59 +++++++++++++++++++++++++++++++++++- slog-handler-guide/guide.md | 22 +++++++++++++- 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/slog-handler-guide/README.md b/slog-handler-guide/README.md index 45dd61ef..b9a48d63 100644 --- a/slog-handler-guide/README.md +++ b/slog-handler-guide/README.md @@ -268,8 +268,65 @@ our handler makes a single call to `h.out.Write` with the buffer we've accumulat We hold the lock for this write to make it atomic with respect to other goroutines that may be calling `Handle` at the same time. -TODO(jba): talk about appendAttr +At the heart of the handler is the `appendAttr` method, responsible for +formatting a single attribute: +``` +func (h *IndentHandler) appendAttr(buf []byte, a slog.Attr, indentLevel int) []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 + } + // Indent 4 spaces per level. + buf = fmt.Appendf(buf, "%*s", indentLevel*4, "") + switch a.Value.Kind() { + case slog.KindString: + // Quote string values, to make them easy to parse. + buf = fmt.Appendf(buf, "%s: %q\n", a.Key, a.Value.String()) + case slog.KindTime: + // Write times in a standard way, without the monotonic time. + buf = fmt.Appendf(buf, "%s: %s\n", a.Key, a.Value.Time().Format(time.RFC3339Nano)) + case slog.KindGroup: + attrs := a.Value.Group() + // Ignore empty groups. + if len(attrs) == 0 { + return buf + } + // If the key is non-empty, write it out and indent the rest of the attrs. + // Otherwise, inline the attrs. + if a.Key != "" { + buf = fmt.Appendf(buf, "%s:\n", a.Key) + indentLevel++ + } + for _, ga := range attrs { + buf = h.appendAttr(buf, ga, indentLevel) + } + default: + buf = fmt.Appendf(buf, "%s: %s\n", a.Key, a.Value) + } + return buf +} +``` + +It begins by resolving the attribute, to run the `LogValuer.LogValue` method of +the value if it has one. All handlers should resolve every attribute they +process. + +Next, it follows the handler rule that says that empty attributes should be +ignored. + +Then it switches on the attribute kind to determine what format to use. For most +(the default case of the switch), it relies on `slog.Value`'s `String` method to +produce something reasonable. It handles strings and times specially: +strings by quoting them, and times by formatting them in a standard way. + +When `appendAttr` sees a `Group`, it calls itself recursively on the group's +attributes, after applying two more handler rules. +First, a group with no attributes is ignored&emdash;not even its key is displayed. +Second, a group with an empty key is inlined: the group boundary isn't marked in +any way. In our case, that means the group's attributes aren't indented. ## The `WithAttrs` method diff --git a/slog-handler-guide/guide.md b/slog-handler-guide/guide.md index 683e367b..345857ce 100644 --- a/slog-handler-guide/guide.md +++ b/slog-handler-guide/guide.md @@ -206,8 +206,28 @@ our handler makes a single call to `h.out.Write` with the buffer we've accumulat We hold the lock for this write to make it atomic with respect to other goroutines that may be calling `Handle` at the same time. -TODO(jba): talk about appendAttr +At the heart of the handler is the `appendAttr` method, responsible for +formatting a single attribute: +%include indenthandler1/indent_handler.go appendAttr - + +It begins by resolving the attribute, to run the `LogValuer.LogValue` method of +the value if it has one. All handlers should resolve every attribute they +process. + +Next, it follows the handler rule that says that empty attributes should be +ignored. + +Then it switches on the attribute kind to determine what format to use. For most +kinds (the default case of the switch), it relies on `slog.Value`'s `String` method to +produce something reasonable. It handles strings and times specially: +strings by quoting them, and times by formatting them in a standard way. + +When `appendAttr` sees a `Group`, it calls itself recursively on the group's +attributes, after applying two more handler rules. +First, a group with no attributes is ignored&emdash;not even its key is displayed. +Second, a group with an empty key is inlined: the group boundary isn't marked in +any way. In our case, that means the group's attributes aren't indented. ## The `WithAttrs` method From f15fe1c962335adaff0ec060f71395b95c8fa125 Mon Sep 17 00:00:00 2001 From: Jonathan Amsterdam Date: Fri, 14 Jul 2023 20:17:10 -0400 Subject: [PATCH 11/48] slog-handler-guide: handler example: Enabled method Change-Id: I193980beda71ab8e20ccdd8a4eb0d72a9d2b32a0 Reviewed-on: https://go-review.googlesource.com/c/example/+/509956 TryBot-Result: Gopher Robot Run-TryBot: Jonathan Amsterdam Reviewed-by: Ian Cottrell --- slog-handler-guide/README.md | 9 ++++++++- slog-handler-guide/guide.md | 5 ++++- slog-handler-guide/indenthandler1/indent_handler.go | 9 +++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/slog-handler-guide/README.md b/slog-handler-guide/README.md index b9a48d63..db36fad0 100644 --- a/slog-handler-guide/README.md +++ b/slog-handler-guide/README.md @@ -174,7 +174,14 @@ A handler's `Enabled` method could report whether the argument level is greater than or equal to the context value, allowing the verbosity of the work done by each request to be controlled independently. -TODO(jba): include Enabled example +Our `IndentHandler` doesn't use the context. It just compares the argument level +with its configured minimum level: + +``` +func (h *IndentHandler) Enabled(ctx context.Context, level slog.Level) bool { + return level >= h.opts.Level.Level() +} +``` ## The `Handle` method diff --git a/slog-handler-guide/guide.md b/slog-handler-guide/guide.md index 345857ce..a87ebc8a 100644 --- a/slog-handler-guide/guide.md +++ b/slog-handler-guide/guide.md @@ -137,7 +137,10 @@ A handler's `Enabled` method could report whether the argument level is greater than or equal to the context value, allowing the verbosity of the work done by each request to be controlled independently. -TODO(jba): include Enabled example +Our `IndentHandler` doesn't use the context. It just compares the argument level +with its configured minimum level: + +%include indenthandler1/indent_handler.go enabled - ## The `Handle` method diff --git a/slog-handler-guide/indenthandler1/indent_handler.go b/slog-handler-guide/indenthandler1/indent_handler.go index 41f1a067..e0373d4a 100644 --- a/slog-handler-guide/indenthandler1/indent_handler.go +++ b/slog-handler-guide/indenthandler1/indent_handler.go @@ -40,10 +40,13 @@ func New(out io.Writer, opts *Options) *IndentHandler { //!-types +// !+enabled func (h *IndentHandler) Enabled(ctx context.Context, level slog.Level) bool { return level >= h.opts.Level.Level() } +//!-enabled + func (h *IndentHandler) WithGroup(name string) slog.Handler { // TODO: implement. return h @@ -54,6 +57,7 @@ func (h *IndentHandler) WithAttrs(attrs []slog.Attr) slog.Handler { return h } +// !+handle func (h *IndentHandler) Handle(ctx context.Context, r slog.Record) error { buf := make([]byte, 0, 1024) if !r.Time.IsZero() { @@ -79,6 +83,9 @@ func (h *IndentHandler) Handle(ctx context.Context, r slog.Record) error { return err } +//!-handle + +// !+appendAttr func (h *IndentHandler) appendAttr(buf []byte, a slog.Attr, indentLevel int) []byte { // Resolve the Attr's value before doing anything else. a.Value = a.Value.Resolve() @@ -115,3 +122,5 @@ func (h *IndentHandler) appendAttr(buf []byte, a slog.Attr, indentLevel int) []b } return buf } + +//!-appendAttr From 183ca0816142e96dd3343c0117797c4ccde27a0f Mon Sep 17 00:00:00 2001 From: Jonathan Amsterdam Date: Thu, 20 Jul 2023 13:13:02 -0400 Subject: [PATCH 12/48] slog-handler-guide: WithXXX methods without pre-formatting Change-Id: Ia95d8ddd50eb72bef057768d88b216d7b9809c58 Reviewed-on: https://go-review.googlesource.com/c/example/+/511576 TryBot-Result: Gopher Robot Run-TryBot: Jonathan Amsterdam Reviewed-by: Ian Cottrell --- slog-handler-guide/README.md | 132 +++++++++++++- slog-handler-guide/guide.md | 53 +++++- .../indenthandler2/indent_handler.go | 164 ++++++++++++++++++ .../indenthandler2/indent_handler_test.go | 157 +++++++++++++++++ 4 files changed, 495 insertions(+), 11 deletions(-) create mode 100644 slog-handler-guide/indenthandler2/indent_handler.go create mode 100644 slog-handler-guide/indenthandler2/indent_handler_test.go diff --git a/slog-handler-guide/README.md b/slog-handler-guide/README.md index db36fad0..4dc72d10 100644 --- a/slog-handler-guide/README.md +++ b/slog-handler-guide/README.md @@ -325,13 +325,13 @@ Next, it follows the handler rule that says that empty attributes should be ignored. Then it switches on the attribute kind to determine what format to use. For most -(the default case of the switch), it relies on `slog.Value`'s `String` method to +kinds (the default case of the switch), it relies on `slog.Value`'s `String` method to produce something reasonable. It handles strings and times specially: strings by quoting them, and times by formatting them in a standard way. When `appendAttr` sees a `Group`, it calls itself recursively on the group's attributes, after applying two more handler rules. -First, a group with no attributes is ignored&emdash;not even its key is displayed. +First, a group with no attributes is ignored—not even its key is displayed. Second, a group with an empty key is inlined: the group boundary isn't marked in any way. In our case, that means the group's attributes aren't indented. @@ -360,8 +360,7 @@ the original handler (its receiver) unchanged. For example, this call: creates a new logger, `logger2`, with an additional attribute, but has no effect on `logger1`. - -We will show an example implementation of `WithAttrs` below, when we discuss `WithGroup`. +We will show example implementations of `WithAttrs` below, when we discuss `WithGroup`. ## The `WithGroup` method @@ -390,7 +389,128 @@ the implementations of `Handler.WithGroup` and `Handler.WithAttrs`. We will look at two implementations of `WithGroup` and `WithAttrs`, one that pre-formats and one that doesn't. -TODO(jba): add IndentHandler examples +### Without pre-formatting + +Our first implementation will collect the information from `WithGroup` and +`WithAttrs` calls to build up a slice of group names and attribute lists, +and loop over that slice in `Handle`. We start with a struct that can hold +either a group name or some attributes: + +``` +// groupOrAttrs holds either a group name or a list of slog.Attrs. +type groupOrAttrs struct { + group string // group name if non-empty + attrs []slog.Attr // attrs if non-empty +} +``` + +Then we add a slice of `groupOrAttrs` to our handler: + +``` +type IndentHandler struct { + opts Options + goas []groupOrAttrs + mu *sync.Mutex + out io.Writer +} +``` + +As stated above, The `WithGroup` and `WithAttrs` methods should not modify their +receiver. +To that end, we define a method that will copy our handler struct +and append one `groupOrAttrs` to the copy: + +``` +func (h *IndentHandler) withGroupOrAttrs(goa groupOrAttrs) *IndentHandler { + h2 := *h + h2.goas = make([]groupOrAttrs, len(h.goas)+1) + copy(h2.goas, h.goas) + h2.goas[len(h2.goas)-1] = goa + return &h2 +} +``` + +Most of the fields of `IndentHandler` can be copied shallowly, but the slice of +`groupOrAttrs` requires a deep copy, or the clone and the original will point to +the same underlying array. If we used `append` instead of making an explicit +copy, we would introduce that subtle aliasing bug. + +Using `withGroupOrAttrs`, the `With` methods are easy: + +``` +func (h *IndentHandler) WithGroup(name string) slog.Handler { + if name == "" { + return h + } + return h.withGroupOrAttrs(groupOrAttrs{group: name}) +} + +func (h *IndentHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + if len(attrs) == 0 { + return h + } + return h.withGroupOrAttrs(groupOrAttrs{attrs: attrs}) +} +``` + +The `Handle` method can now process the groupOrAttrs slice after +the built-in attributes and before the ones in the record: + +``` +func (h *IndentHandler) Handle(ctx context.Context, r slog.Record) error { + buf := make([]byte, 0, 1024) + if !r.Time.IsZero() { + buf = h.appendAttr(buf, slog.Time(slog.TimeKey, r.Time), 0) + } + buf = h.appendAttr(buf, slog.Any(slog.LevelKey, r.Level), 0) + if r.PC != 0 { + fs := runtime.CallersFrames([]uintptr{r.PC}) + f, _ := fs.Next() + buf = h.appendAttr(buf, slog.String(slog.SourceKey, fmt.Sprintf("%s:%d", f.File, f.Line)), 0) + } + buf = h.appendAttr(buf, slog.String(slog.MessageKey, r.Message), 0) + indentLevel := 0 + // Handle state from WithGroup and WithAttrs. + goas := h.goas + if r.NumAttrs() == 0 { + // If the record has no Attrs, remove groups at the end of the list; they are empty. + for len(goas) > 0 && goas[len(goas)-1].group != "" { + goas = goas[:len(goas)-1] + } + } + for _, goa := range goas { + if goa.group != "" { + buf = fmt.Appendf(buf, "%*s%s:\n", indentLevel*4, "", goa.group) + indentLevel++ + } else { + for _, a := range goa.attrs { + buf = h.appendAttr(buf, a, indentLevel) + } + } + } + r.Attrs(func(a slog.Attr) bool { + buf = h.appendAttr(buf, a, indentLevel) + return true + }) + buf = append(buf, "---\n"...) + h.mu.Lock() + defer h.mu.Unlock() + _, err := h.out.Write(buf) + return err +} +``` + +You may have noticed that our algorithm for +recording `WithGroup` and `WithAttrs` information is quadratic in the +number of calls to those methods, because of the repeated copying. +That is unlikely to matter in practice, but if it bothers you, +you can use a linked list instead, +which `Handle` will have to reverse or visit recursively. +See [github.com/jba/slog/withsupport](https://github.com/jba/slog/withsupport) for an implementation. + +### With pre-formatting + +TODO(jba): write ## Testing @@ -465,7 +585,7 @@ to format a value will probably switch on the value's kind: What should happen in the default case, when the handler encounters a `Kind` that it doesn't know about? The built-in handlers try to muddle through by using the result of the value's -`String` method. +`String` method, as our example handler does. They do not panic or return an error. Your own handlers might in addition want to report the problem through your production monitoring or error-tracking telemetry system. diff --git a/slog-handler-guide/guide.md b/slog-handler-guide/guide.md index a87ebc8a..922cdd74 100644 --- a/slog-handler-guide/guide.md +++ b/slog-handler-guide/guide.md @@ -228,7 +228,7 @@ strings by quoting them, and times by formatting them in a standard way. When `appendAttr` sees a `Group`, it calls itself recursively on the group's attributes, after applying two more handler rules. -First, a group with no attributes is ignored&emdash;not even its key is displayed. +First, a group with no attributes is ignored—not even its key is displayed. Second, a group with an empty key is inlined: the group boundary isn't marked in any way. In our case, that means the group's attributes aren't indented. @@ -257,8 +257,7 @@ the original handler (its receiver) unchanged. For example, this call: creates a new logger, `logger2`, with an additional attribute, but has no effect on `logger1`. - -We will show an example implementation of `WithAttrs` below, when we discuss `WithGroup`. +We will show example implementations of `WithAttrs` below, when we discuss `WithGroup`. ## The `WithGroup` method @@ -287,7 +286,51 @@ the implementations of `Handler.WithGroup` and `Handler.WithAttrs`. We will look at two implementations of `WithGroup` and `WithAttrs`, one that pre-formats and one that doesn't. -TODO(jba): add IndentHandler examples +### Without pre-formatting + +Our first implementation will collect the information from `WithGroup` and +`WithAttrs` calls to build up a slice of group names and attribute lists, +and loop over that slice in `Handle`. We start with a struct that can hold +either a group name or some attributes: + +%include indenthandler2/indent_handler.go gora - + +Then we add a slice of `groupOrAttrs` to our handler: + +%include indenthandler2/indent_handler.go IndentHandler - + +As stated above, The `WithGroup` and `WithAttrs` methods should not modify their +receiver. +To that end, we define a method that will copy our handler struct +and append one `groupOrAttrs` to the copy: + +%include indenthandler2/indent_handler.go withgora - + +Most of the fields of `IndentHandler` can be copied shallowly, but the slice of +`groupOrAttrs` requires a deep copy, or the clone and the original will point to +the same underlying array. If we used `append` instead of making an explicit +copy, we would introduce that subtle aliasing bug. + +Using `withGroupOrAttrs`, the `With` methods are easy: + +%include indenthandler2/indent_handler.go withs - + +The `Handle` method can now process the groupOrAttrs slice after +the built-in attributes and before the ones in the record: + +%include indenthandler2/indent_handler.go handle - + +You may have noticed that our algorithm for +recording `WithGroup` and `WithAttrs` information is quadratic in the +number of calls to those methods, because of the repeated copying. +That is unlikely to matter in practice, but if it bothers you, +you can use a linked list instead, +which `Handle` will have to reverse or visit recursively. +See [github.com/jba/slog/withsupport](https://github.com/jba/slog/withsupport) for an implementation. + +### With pre-formatting + +TODO(jba): write ## Testing @@ -362,7 +405,7 @@ to format a value will probably switch on the value's kind: What should happen in the default case, when the handler encounters a `Kind` that it doesn't know about? The built-in handlers try to muddle through by using the result of the value's -`String` method. +`String` method, as our example handler does. They do not panic or return an error. Your own handlers might in addition want to report the problem through your production monitoring or error-tracking telemetry system. diff --git a/slog-handler-guide/indenthandler2/indent_handler.go b/slog-handler-guide/indenthandler2/indent_handler.go new file mode 100644 index 00000000..459ed465 --- /dev/null +++ b/slog-handler-guide/indenthandler2/indent_handler.go @@ -0,0 +1,164 @@ +//go:build go1.21 + +package indenthandler + +import ( + "context" + "fmt" + "io" + "log/slog" + "runtime" + "sync" + "time" +) + +// !+IndentHandler +type IndentHandler struct { + opts Options + goas []groupOrAttrs + mu *sync.Mutex + out io.Writer +} + +//!-IndentHandler + +type Options struct { + // Level reports the minimum level to log. + // Levels with lower levels are discarded. + // If nil, the Handler uses [slog.LevelInfo]. + Level slog.Leveler +} + +// !+gora +// groupOrAttrs holds either a group name or a list of slog.Attrs. +type groupOrAttrs struct { + group string // group name if non-empty + attrs []slog.Attr // attrs if non-empty +} + +//!-gora + +func New(out io.Writer, opts *Options) *IndentHandler { + h := &IndentHandler{out: out, mu: &sync.Mutex{}} + if opts != nil { + h.opts = *opts + } + if h.opts.Level == nil { + h.opts.Level = slog.LevelInfo + } + return h +} + +func (h *IndentHandler) Enabled(ctx context.Context, level slog.Level) bool { + return level >= h.opts.Level.Level() +} + +// !+withs +func (h *IndentHandler) WithGroup(name string) slog.Handler { + if name == "" { + return h + } + return h.withGroupOrAttrs(groupOrAttrs{group: name}) +} + +func (h *IndentHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + if len(attrs) == 0 { + return h + } + return h.withGroupOrAttrs(groupOrAttrs{attrs: attrs}) +} + +//!-withs + +// !+withgora +func (h *IndentHandler) withGroupOrAttrs(goa groupOrAttrs) *IndentHandler { + h2 := *h + h2.goas = make([]groupOrAttrs, len(h.goas)+1) + copy(h2.goas, h.goas) + h2.goas[len(h2.goas)-1] = goa + return &h2 +} + +//!-withgora + +// !+handle +func (h *IndentHandler) Handle(ctx context.Context, r slog.Record) error { + buf := make([]byte, 0, 1024) + if !r.Time.IsZero() { + buf = h.appendAttr(buf, slog.Time(slog.TimeKey, r.Time), 0) + } + buf = h.appendAttr(buf, slog.Any(slog.LevelKey, r.Level), 0) + if r.PC != 0 { + fs := runtime.CallersFrames([]uintptr{r.PC}) + f, _ := fs.Next() + buf = h.appendAttr(buf, slog.String(slog.SourceKey, fmt.Sprintf("%s:%d", f.File, f.Line)), 0) + } + buf = h.appendAttr(buf, slog.String(slog.MessageKey, r.Message), 0) + indentLevel := 0 + // Handle state from WithGroup and WithAttrs. + goas := h.goas + if r.NumAttrs() == 0 { + // If the record has no Attrs, remove groups at the end of the list; they are empty. + for len(goas) > 0 && goas[len(goas)-1].group != "" { + goas = goas[:len(goas)-1] + } + } + for _, goa := range goas { + if goa.group != "" { + buf = fmt.Appendf(buf, "%*s%s:\n", indentLevel*4, "", goa.group) + indentLevel++ + } else { + for _, a := range goa.attrs { + buf = h.appendAttr(buf, a, indentLevel) + } + } + } + r.Attrs(func(a slog.Attr) bool { + buf = h.appendAttr(buf, a, indentLevel) + return true + }) + buf = append(buf, "---\n"...) + h.mu.Lock() + defer h.mu.Unlock() + _, err := h.out.Write(buf) + return err +} + +//!-handle + +func (h *IndentHandler) appendAttr(buf []byte, a slog.Attr, indentLevel int) []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 + } + // Indent 4 spaces per level. + buf = fmt.Appendf(buf, "%*s", indentLevel*4, "") + switch a.Value.Kind() { + case slog.KindString: + // Quote string values, to make them easy to parse. + buf = fmt.Appendf(buf, "%s: %q\n", a.Key, a.Value.String()) + case slog.KindTime: + // Write times in a standard way, without the monotonic time. + buf = fmt.Appendf(buf, "%s: %s\n", a.Key, a.Value.Time().Format(time.RFC3339Nano)) + case slog.KindGroup: + attrs := a.Value.Group() + // Ignore empty groups. + if len(attrs) == 0 { + return buf + } + // If the key is non-empty, write it out and indent the rest of the attrs. + // Otherwise, inline the attrs. + if a.Key != "" { + buf = fmt.Appendf(buf, "%s:\n", a.Key) + indentLevel++ + } + for _, ga := range attrs { + buf = h.appendAttr(buf, ga, indentLevel) + } + default: + buf = fmt.Appendf(buf, "%s: %s\n", a.Key, a.Value) + } + return buf +} diff --git a/slog-handler-guide/indenthandler2/indent_handler_test.go b/slog-handler-guide/indenthandler2/indent_handler_test.go new file mode 100644 index 00000000..40633525 --- /dev/null +++ b/slog-handler-guide/indenthandler2/indent_handler_test.go @@ -0,0 +1,157 @@ +//go:build go1.21 + +package indenthandler + +import ( + "bufio" + "bytes" + "fmt" + "reflect" + "regexp" + "strconv" + "strings" + "testing" + "unicode" + + "log/slog" + "testing/slogtest" +) + +func TestSlogtest(t *testing.T) { + var buf bytes.Buffer + err := slogtest.TestHandler(New(&buf, nil), func() []map[string]any { + return parseLogEntries(buf.String()) + }) + if err != nil { + t.Error(err) + } +} + +func Test(t *testing.T) { + var buf bytes.Buffer + l := slog.New(New(&buf, nil)) + l.Info("hello", "a", 1, "b", true, "c", 3.14, slog.Group("g", "h", 1, "i", 2), "d", "NO") + got := buf.String() + wantre := `time: [-0-9T:.]+Z? +level: INFO +source: ".*/indent_handler_test.go:\d+" +msg: "hello" +a: 1 +b: true +c: 3.14 +g: + h: 1 + i: 2 +d: "NO" +` + re := regexp.MustCompile(wantre) + if !re.MatchString(got) { + t.Errorf("\ngot:\n%q\nwant:\n%q", got, wantre) + } + + buf.Reset() + l.Debug("test") + if got := buf.Len(); got != 0 { + t.Errorf("got buf.Len() = %d, want 0", got) + } +} + +func parseLogEntries(s string) []map[string]any { + var ms []map[string]any + scan := bufio.NewScanner(strings.NewReader(s)) + for scan.Scan() { + m := parseGroup(scan) + ms = append(ms, m) + } + if scan.Err() != nil { + panic(scan.Err()) + } + return ms +} + +func parseGroup(scan *bufio.Scanner) map[string]any { + m := map[string]any{} + groupIndent := -1 + for { + line := scan.Text() + if line == "---" { // end of entry + break + } + k, v, found := strings.Cut(line, ":") + if !found { + panic(fmt.Sprintf("no ':' in line %q", line)) + } + indent := strings.IndexFunc(k, func(r rune) bool { + return !unicode.IsSpace(r) + }) + if indent < 0 { + panic("blank line") + } + if groupIndent < 0 { + // First line in group; remember the indent. + groupIndent = indent + } else if indent < groupIndent { + // End of group + break + } else if indent > groupIndent { + panic(fmt.Sprintf("indent increased on line %q", line)) + } + + key := strings.TrimSpace(k) + if v == "" { + // Just a key: start of a group. + if !scan.Scan() { + panic("empty group") + } + m[key] = parseGroup(scan) + } else { + v = strings.TrimSpace(v) + if len(v) > 0 && v[0] == '"' { + var err error + v, err = strconv.Unquote(v) + if err != nil { + panic(err) + } + } + m[key] = v + if !scan.Scan() { + break + } + } + } + return m +} + +func TestParseLogEntries(t *testing.T) { + in := ` +a: 1 +b: 2 +c: 3 +g: + h: 4 + i: 5 +d: 6 +--- +e: 7 +--- +` + want := []map[string]any{ + { + "a": "1", + "b": "2", + "c": "3", + "g": map[string]any{ + "h": "4", + "i": "5", + }, + "d": "6", + }, + { + "e": "7", + }, + } + got := parseLogEntries(in[1:]) + if !reflect.DeepEqual(got, want) { + t.Errorf("\ngot:\n%v\nwant:\n%v", got, want) + } +} From 94c9d89053b7e95bc53800ab18c66d5b094d5d67 Mon Sep 17 00:00:00 2001 From: Jonathan Amsterdam Date: Fri, 21 Jul 2023 14:08:08 -0400 Subject: [PATCH 13/48] slog-handler-guide: preformatting Change-Id: Ib1e64bafc76c296aa0ffb40d754d6e41fe69a33e Reviewed-on: https://go-review.googlesource.com/c/example/+/511638 Run-TryBot: Jonathan Amsterdam TryBot-Result: Gopher Robot Reviewed-by: Ian Cottrell --- slog-handler-guide/README.md | 169 +++++++++++++++++- slog-handler-guide/guide.md | 92 +++++++++- .../indenthandler3/indent_handler.go | 162 +++++++++++++++++ .../indenthandler3/indent_handler_test.go | 157 ++++++++++++++++ 4 files changed, 578 insertions(+), 2 deletions(-) create mode 100644 slog-handler-guide/indenthandler3/indent_handler.go create mode 100644 slog-handler-guide/indenthandler3/indent_handler_test.go diff --git a/slog-handler-guide/README.md b/slog-handler-guide/README.md index 4dc72d10..ff74e088 100644 --- a/slog-handler-guide/README.md +++ b/slog-handler-guide/README.md @@ -510,7 +510,174 @@ See [github.com/jba/slog/withsupport](https://github.com/jba/slog/withsupport) f ### With pre-formatting -TODO(jba): write +Our second implementation implements pre-formatting. +This implementation is more complicated than the previous one. +Is the extra complexity worth it? +That depends on your circumstances, but here is one circumstance where +it might be. +Say that you wanted your server to log a lot of information about an incoming +request with every log message that happens during that request. A typical +handler might look something like this: + + func (s *Server) handleWidgets(w http.ResponseWriter, r *http.Request) { + logger := s.logger.With( + "url", r.URL, + "traceID": r.Header.Get("X-Cloud-Trace-Context"), + // many other attributes + ) + // ... + } + +A single handleWidgets request might generate hundreds of log lines. +For instance, it might contain code like this: + + for _, w := range widgets { + logger.Info("processing widget", "name", w.Name) + // ... + } + +For every such line, the `Handle` method we wrote above will format all +the attributes that were added using `With` above, in addition to the +ones on the log line itself. + +Maybe all that extra work doesn't slow down your server significantly, because +it does so much other work that time spent logging is just noise. +But perhaps your server is fast enough that all that extra formatting appears high up +in your CPU profiles. That is when pre-formatting can make a big difference, +by formatting the attributes in a call to `With` just once. + +To pre-format the arguments to `WithAttrs`, we need to keep track of some +additional state in the `IndentHandler` struct. + +``` +type IndentHandler struct { + opts Options + preformatted []byte // data from WithGroup and WithAttrs + unopenedGroups []string // groups from WithGroup that haven't been opened + indentLevel int // same as number of opened groups so far + mu *sync.Mutex + out io.Writer +} +``` + +Mainly, we need a buffer to hold the pre-formatted data. +But we also need to keep track of which groups +we've seen but haven't output yet. We'll call those groups "unopened." +We also need to track how many groups we've opened, which we can do +with a simple counter, since an opened group's only effect is to change the +indentation level. + +The `WithGroup` implementation is a lot like the previous one: just remember the +new group, which is unopened initially. + +``` +func (h *IndentHandler) WithGroup(name string) slog.Handler { + if name == "" { + return h + } + h2 := *h + // Add an unopened group to h2 without modifying h. + h2.unopenedGroups = make([]string, len(h.unopenedGroups)+1) + copy(h2.unopenedGroups, h.unopenedGroups) + h2.unopenedGroups[len(h2.unopenedGroups)-1] = name + return &h2 +} +``` + +`WithAttrs` does all the pre-formatting: + +``` +func (h *IndentHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + if len(attrs) == 0 { + return h + } + h2 := *h + // Force an append to copy the underlying array. + pre := slices.Clip(h.preformatted) + // Add all groups from WithGroup that haven't already been added. + h2.preformatted = h2.appendUnopenedGroups(pre, h2.indentLevel) + // Each of those groups increased the indent level by 1. + h2.indentLevel += len(h2.unopenedGroups) + // Now all groups have been opened. + h2.unopenedGroups = nil + // Pre-format the attributes. + for _, a := range attrs { + h2.preformatted = h2.appendAttr(h2.preformatted, a, h2.indentLevel) + } + return &h2 +} + +func (h *IndentHandler) appendUnopenedGroups(buf []byte, indentLevel int) []byte { + for _, g := range h.unopenedGroups { + buf = fmt.Appendf(buf, "%*s%s:\n", indentLevel*4, "", g) + indentLevel++ + } + return buf +} +``` + +It first opens any unopened groups. This handles calls like: + + logger.WithGroup("g").WithGroup("h").With("a", 1) + +Here, `WithAttrs` must output "g" and "h" before "a". Since a group established +by `WithGroup` is in effect for the rest of the log line, `WithAttrs` increments +the indentation level for each group it opens. + +Lastly, `WithAttrs` formats its argument attributes, using the same `appendAttr` +method we saw above. + +It's the `Handle` method's job to insert the pre-formatted material in the right +place, which is after the built-in attributes and before the ones in the record: + +``` +func (h *IndentHandler) Handle(ctx context.Context, r slog.Record) error { + buf := make([]byte, 0, 1024) + if !r.Time.IsZero() { + buf = h.appendAttr(buf, slog.Time(slog.TimeKey, r.Time), 0) + } + buf = h.appendAttr(buf, slog.Any(slog.LevelKey, r.Level), 0) + if r.PC != 0 { + fs := runtime.CallersFrames([]uintptr{r.PC}) + f, _ := fs.Next() + buf = h.appendAttr(buf, slog.String(slog.SourceKey, fmt.Sprintf("%s:%d", f.File, f.Line)), 0) + } + buf = h.appendAttr(buf, slog.String(slog.MessageKey, r.Message), 0) + // Insert preformatted attributes just after built-in ones. + buf = append(buf, h.preformatted...) + if r.NumAttrs() > 0 { + buf = h.appendUnopenedGroups(buf, h.indentLevel) + r.Attrs(func(a slog.Attr) bool { + buf = h.appendAttr(buf, a, h.indentLevel+len(h.unopenedGroups)) + return true + }) + } + buf = append(buf, "---\n"...) + h.mu.Lock() + defer h.mu.Unlock() + _, err := h.out.Write(buf) + return err +} +``` + +It must also open any groups that haven't yet been opened. The logic covers +log lines like this one: + + logger.WithGroup("g").Info("msg", "a", 1) + +where "g" is unopened before `Handle` is called and must be written to produce +the correct output: + + level: INFO + msg: "msg" + g: + a: 1 + +The check for `r.NumAttrs() > 0` handles this case: + + logger.WithGroup("g").Info("msg") + +Here there are no record attributes, so no group to open. ## Testing diff --git a/slog-handler-guide/guide.md b/slog-handler-guide/guide.md index 922cdd74..f9b9027f 100644 --- a/slog-handler-guide/guide.md +++ b/slog-handler-guide/guide.md @@ -330,7 +330,97 @@ See [github.com/jba/slog/withsupport](https://github.com/jba/slog/withsupport) f ### With pre-formatting -TODO(jba): write +Our second implementation implements pre-formatting. +This implementation is more complicated than the previous one. +Is the extra complexity worth it? +That depends on your circumstances, but here is one circumstance where +it might be. +Say that you wanted your server to log a lot of information about an incoming +request with every log message that happens during that request. A typical +handler might look something like this: + + func (s *Server) handleWidgets(w http.ResponseWriter, r *http.Request) { + logger := s.logger.With( + "url", r.URL, + "traceID": r.Header.Get("X-Cloud-Trace-Context"), + // many other attributes + ) + // ... + } + +A single handleWidgets request might generate hundreds of log lines. +For instance, it might contain code like this: + + for _, w := range widgets { + logger.Info("processing widget", "name", w.Name) + // ... + } + +For every such line, the `Handle` method we wrote above will format all +the attributes that were added using `With` above, in addition to the +ones on the log line itself. + +Maybe all that extra work doesn't slow down your server significantly, because +it does so much other work that time spent logging is just noise. +But perhaps your server is fast enough that all that extra formatting appears high up +in your CPU profiles. That is when pre-formatting can make a big difference, +by formatting the attributes in a call to `With` just once. + +To pre-format the arguments to `WithAttrs`, we need to keep track of some +additional state in the `IndentHandler` struct. + +%include indenthandler3/indent_handler.go IndentHandler - + +Mainly, we need a buffer to hold the pre-formatted data. +But we also need to keep track of which groups +we've seen but haven't output yet. We'll call those groups "unopened." +We also need to track how many groups we've opened, which we can do +with a simple counter, since an opened group's only effect is to change the +indentation level. + +The `WithGroup` implementation is a lot like the previous one: just remember the +new group, which is unopened initially. + +%include indenthandler3/indent_handler.go WithGroup - + +`WithAttrs` does all the pre-formatting: + +%include indenthandler3/indent_handler.go WithAttrs - + +It first opens any unopened groups. This handles calls like: + + logger.WithGroup("g").WithGroup("h").With("a", 1) + +Here, `WithAttrs` must output "g" and "h" before "a". Since a group established +by `WithGroup` is in effect for the rest of the log line, `WithAttrs` increments +the indentation level for each group it opens. + +Lastly, `WithAttrs` formats its argument attributes, using the same `appendAttr` +method we saw above. + +It's the `Handle` method's job to insert the pre-formatted material in the right +place, which is after the built-in attributes and before the ones in the record: + +%include indenthandler3/indent_handler.go Handle - + +It must also open any groups that haven't yet been opened. The logic covers +log lines like this one: + + logger.WithGroup("g").Info("msg", "a", 1) + +where "g" is unopened before `Handle` is called and must be written to produce +the correct output: + + level: INFO + msg: "msg" + g: + a: 1 + +The check for `r.NumAttrs() > 0` handles this case: + + logger.WithGroup("g").Info("msg") + +Here there are no record attributes, so no group to open. ## Testing diff --git a/slog-handler-guide/indenthandler3/indent_handler.go b/slog-handler-guide/indenthandler3/indent_handler.go new file mode 100644 index 00000000..885d1a48 --- /dev/null +++ b/slog-handler-guide/indenthandler3/indent_handler.go @@ -0,0 +1,162 @@ +//go:build go1.21 + +package indenthandler + +import ( + "context" + "fmt" + "io" + "log/slog" + "runtime" + "slices" + "sync" + "time" +) + +// !+IndentHandler +type IndentHandler struct { + opts Options + preformatted []byte // data from WithGroup and WithAttrs + unopenedGroups []string // groups from WithGroup that haven't been opened + indentLevel int // same as number of opened groups so far + mu *sync.Mutex + out io.Writer +} + +//!-IndentHandler + +type Options struct { + // Level reports the minimum level to log. + // Levels with lower levels are discarded. + // If nil, the Handler uses [slog.LevelInfo]. + Level slog.Leveler +} + +func New(out io.Writer, opts *Options) *IndentHandler { + h := &IndentHandler{out: out, mu: &sync.Mutex{}} + if opts != nil { + h.opts = *opts + } + if h.opts.Level == nil { + h.opts.Level = slog.LevelInfo + } + return h +} + +func (h *IndentHandler) Enabled(ctx context.Context, level slog.Level) bool { + return level >= h.opts.Level.Level() +} + +// !+WithGroup +func (h *IndentHandler) WithGroup(name string) slog.Handler { + if name == "" { + return h + } + h2 := *h + // Add an unopened group to h2 without modifying h. + h2.unopenedGroups = make([]string, len(h.unopenedGroups)+1) + copy(h2.unopenedGroups, h.unopenedGroups) + h2.unopenedGroups[len(h2.unopenedGroups)-1] = name + return &h2 +} + +//!-WithGroup + +// !+WithAttrs +func (h *IndentHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + if len(attrs) == 0 { + return h + } + h2 := *h + // Force an append to copy the underlying array. + pre := slices.Clip(h.preformatted) + // Add all groups from WithGroup that haven't already been added. + h2.preformatted = h2.appendUnopenedGroups(pre, h2.indentLevel) + // Each of those groups increased the indent level by 1. + h2.indentLevel += len(h2.unopenedGroups) + // Now all groups have been opened. + h2.unopenedGroups = nil + // Pre-format the attributes. + for _, a := range attrs { + h2.preformatted = h2.appendAttr(h2.preformatted, a, h2.indentLevel) + } + return &h2 +} + +func (h *IndentHandler) appendUnopenedGroups(buf []byte, indentLevel int) []byte { + for _, g := range h.unopenedGroups { + buf = fmt.Appendf(buf, "%*s%s:\n", indentLevel*4, "", g) + indentLevel++ + } + return buf +} + +//!-WithAttrs + +// !+Handle +func (h *IndentHandler) Handle(ctx context.Context, r slog.Record) error { + buf := make([]byte, 0, 1024) + if !r.Time.IsZero() { + buf = h.appendAttr(buf, slog.Time(slog.TimeKey, r.Time), 0) + } + buf = h.appendAttr(buf, slog.Any(slog.LevelKey, r.Level), 0) + if r.PC != 0 { + fs := runtime.CallersFrames([]uintptr{r.PC}) + f, _ := fs.Next() + buf = h.appendAttr(buf, slog.String(slog.SourceKey, fmt.Sprintf("%s:%d", f.File, f.Line)), 0) + } + buf = h.appendAttr(buf, slog.String(slog.MessageKey, r.Message), 0) + // Insert preformatted attributes just after built-in ones. + buf = append(buf, h.preformatted...) + if r.NumAttrs() > 0 { + buf = h.appendUnopenedGroups(buf, h.indentLevel) + r.Attrs(func(a slog.Attr) bool { + buf = h.appendAttr(buf, a, h.indentLevel+len(h.unopenedGroups)) + return true + }) + } + buf = append(buf, "---\n"...) + h.mu.Lock() + defer h.mu.Unlock() + _, err := h.out.Write(buf) + return err +} + +//!-Handle + +func (h *IndentHandler) appendAttr(buf []byte, a slog.Attr, indentLevel int) []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 + } + // Indent 4 spaces per level. + buf = fmt.Appendf(buf, "%*s", indentLevel*4, "") + switch a.Value.Kind() { + case slog.KindString: + // Quote string values, to make them easy to parse. + buf = fmt.Appendf(buf, "%s: %q\n", a.Key, a.Value.String()) + case slog.KindTime: + // Write times in a standard way, without the monotonic time. + buf = fmt.Appendf(buf, "%s: %s\n", a.Key, a.Value.Time().Format(time.RFC3339Nano)) + case slog.KindGroup: + attrs := a.Value.Group() + // Ignore empty groups. + if len(attrs) == 0 { + return buf + } + // If the key is non-empty, write it out and indent the rest of the attrs. + // Otherwise, inline the attrs. + if a.Key != "" { + buf = fmt.Appendf(buf, "%s:\n", a.Key) + indentLevel++ + } + for _, ga := range attrs { + buf = h.appendAttr(buf, ga, indentLevel) + } + default: + buf = fmt.Appendf(buf, "%s: %s\n", a.Key, a.Value) + } + return buf +} diff --git a/slog-handler-guide/indenthandler3/indent_handler_test.go b/slog-handler-guide/indenthandler3/indent_handler_test.go new file mode 100644 index 00000000..f67bd032 --- /dev/null +++ b/slog-handler-guide/indenthandler3/indent_handler_test.go @@ -0,0 +1,157 @@ +//go:build go1.21 + +package indenthandler + +import ( + "bufio" + "bytes" + "fmt" + "reflect" + "regexp" + "strconv" + "strings" + "testing" + "testing/slogtest" + "unicode" + + "log/slog" +) + +func TestSlogtest(t *testing.T) { + var buf bytes.Buffer + err := slogtest.TestHandler(New(&buf, nil), func() []map[string]any { + return parseLogEntries(buf.String()) + }) + if err != nil { + t.Error(err) + } +} + +func Test(t *testing.T) { + var buf bytes.Buffer + l := slog.New(New(&buf, nil)) + l.Info("hello", "a", 1, "b", true, "c", 3.14, slog.Group("g", "h", 1, "i", 2), "d", "NO") + got := buf.String() + wantre := `time: [-0-9T:.]+Z? +level: INFO +source: ".*/indent_handler_test.go:\d+" +msg: "hello" +a: 1 +b: true +c: 3.14 +g: + h: 1 + i: 2 +d: "NO" +` + re := regexp.MustCompile(wantre) + if !re.MatchString(got) { + t.Errorf("\ngot:\n%q\nwant:\n%q", got, wantre) + } + + buf.Reset() + l.Debug("test") + if got := buf.Len(); got != 0 { + t.Errorf("got buf.Len() = %d, want 0", got) + } +} + +func parseLogEntries(s string) []map[string]any { + var ms []map[string]any + scan := bufio.NewScanner(strings.NewReader(s)) + for scan.Scan() { + m := parseGroup(scan) + ms = append(ms, m) + } + if scan.Err() != nil { + panic(scan.Err()) + } + return ms +} + +func parseGroup(scan *bufio.Scanner) map[string]any { + m := map[string]any{} + groupIndent := -1 + for { + line := scan.Text() + if line == "---" { // end of entry + break + } + k, v, found := strings.Cut(line, ":") + if !found { + panic(fmt.Sprintf("no ':' in line %q", line)) + } + indent := strings.IndexFunc(k, func(r rune) bool { + return !unicode.IsSpace(r) + }) + if indent < 0 { + panic("blank line") + } + if groupIndent < 0 { + // First line in group; remember the indent. + groupIndent = indent + } else if indent < groupIndent { + // End of group + break + } else if indent > groupIndent { + panic(fmt.Sprintf("indent increased on line %q", line)) + } + + key := strings.TrimSpace(k) + if v == "" { + // Just a key: start of a group. + if !scan.Scan() { + panic("empty group") + } + m[key] = parseGroup(scan) + } else { + v = strings.TrimSpace(v) + if len(v) > 0 && v[0] == '"' { + var err error + v, err = strconv.Unquote(v) + if err != nil { + panic(err) + } + } + m[key] = v + if !scan.Scan() { + break + } + } + } + return m +} + +func TestParseLogEntries(t *testing.T) { + in := ` +a: 1 +b: 2 +c: 3 +g: + h: 4 + i: 5 +d: 6 +--- +e: 7 +--- +` + want := []map[string]any{ + { + "a": "1", + "b": "2", + "c": "3", + "g": map[string]any{ + "h": "4", + "i": "5", + }, + "d": "6", + }, + { + "e": "7", + }, + } + got := parseLogEntries(in[1:]) + if !reflect.DeepEqual(got, want) { + t.Errorf("\ngot:\n%v\nwant:\n%v", got, want) + } +} From 4228a155ffa6050c064eb66a8c64e8ca0685d9c8 Mon Sep 17 00:00:00 2001 From: Jonathan Amsterdam Date: Wed, 26 Jul 2023 05:40:22 -0400 Subject: [PATCH 14/48] slog-handler-guide: explain pointer to mutex Change-Id: Icfca54f04ff1fcc2c2b36cb0ccde1efd49e034f4 Reviewed-on: https://go-review.googlesource.com/c/example/+/512999 Reviewed-by: Ian Cottrell Run-TryBot: Jonathan Amsterdam TryBot-Result: Gopher Robot --- slog-handler-guide/README.md | 37 +++++++++++++++++++++++++++++++----- slog-handler-guide/guide.md | 37 +++++++++++++++++++++++++++++++----- 2 files changed, 64 insertions(+), 10 deletions(-) diff --git a/slog-handler-guide/README.md b/slog-handler-guide/README.md index ff74e088..45d0f8b8 100644 --- a/slog-handler-guide/README.md +++ b/slog-handler-guide/README.md @@ -150,9 +150,7 @@ Changes to `LevelVar`s are goroutine-safe. The mutex will be used to ensure that writes to the `io.Writer` happen atomically. Unusually, `IndentHandler` holds a pointer to a `sync.Mutex` rather than holding a `sync.Mutex` directly. -There is a good reason for that, which we'll explain later. - -TODO(jba): add link to that later explanation. +There is a good reason for that, which we'll explain [later](#getting-the-mutex-right). Our handler will need additional state to track calls to `WithGroup` and `WithAttrs`. We will describe that state when we get to those methods. @@ -508,6 +506,33 @@ you can use a linked list instead, which `Handle` will have to reverse or visit recursively. See [github.com/jba/slog/withsupport](https://github.com/jba/slog/withsupport) for an implementation. +#### Getting the mutex right + +Let us revisit the last few lines of `Handle`: + + h.mu.Lock() + defer h.mu.Unlock() + _, err := h.out.Write(buf) + return err + +This code hasn't changed, but we can now appreciate why `h.mu` is a +pointer to a `sync.Mutex`. Both `WithGroup` and `WithAttrs` copy the handler. +Both copies point to the same mutex. +If the copy and the original used different mutexes and were used concurrently, +then their output could be interleaved, or some output could be lost. +Code like this: + + l2 := l1.With("a", 1) + go l1.Info("hello") + l2.Info("goodbye") + +could produce output like this: + + hegoollo a=dbye1 + +See [this bug report](https://go.dev/issue/61321) for more detail. + + ### With pre-formatting Our second implementation implements pre-formatting. @@ -708,8 +733,10 @@ mutable state. - The `Handle` method typically works only with its arguments and stored fields. Calls to output methods like `io.Writer.Write` should be synchronized unless -it can be verified that no locking is needed. Beware of facile claims like -"Unix writes are atomic"; the situation is a lot more nuanced than that. +it can be verified that no locking is needed. +As we saw in our example, storing a pointer to a mutex enables a logger and +all of its clones to synchronize with each other. +Beware of facile claims like "Unix writes are atomic"; the situation is a lot more nuanced than that. Some handlers have legitimate reasons for keeping state. For example, a handler might support a `SetLevel` method to change its configured level diff --git a/slog-handler-guide/guide.md b/slog-handler-guide/guide.md index f9b9027f..90554f5a 100644 --- a/slog-handler-guide/guide.md +++ b/slog-handler-guide/guide.md @@ -113,9 +113,7 @@ Changes to `LevelVar`s are goroutine-safe. The mutex will be used to ensure that writes to the `io.Writer` happen atomically. Unusually, `IndentHandler` holds a pointer to a `sync.Mutex` rather than holding a `sync.Mutex` directly. -There is a good reason for that, which we'll explain later. - -TODO(jba): add link to that later explanation. +There is a good reason for that, which we'll explain [later](#getting-the-mutex-right). Our handler will need additional state to track calls to `WithGroup` and `WithAttrs`. We will describe that state when we get to those methods. @@ -328,6 +326,33 @@ you can use a linked list instead, which `Handle` will have to reverse or visit recursively. See [github.com/jba/slog/withsupport](https://github.com/jba/slog/withsupport) for an implementation. +#### Getting the mutex right + +Let us revisit the last few lines of `Handle`: + + h.mu.Lock() + defer h.mu.Unlock() + _, err := h.out.Write(buf) + return err + +This code hasn't changed, but we can now appreciate why `h.mu` is a +pointer to a `sync.Mutex`. Both `WithGroup` and `WithAttrs` copy the handler. +Both copies point to the same mutex. +If the copy and the original used different mutexes and were used concurrently, +then their output could be interleaved, or some output could be lost. +Code like this: + + l2 := l1.With("a", 1) + go l1.Info("hello") + l2.Info("goodbye") + +could produce output like this: + + hegoollo a=dbye1 + +See [this bug report](https://go.dev/issue/61321) for more detail. + + ### With pre-formatting Our second implementation implements pre-formatting. @@ -451,8 +476,10 @@ mutable state. - The `Handle` method typically works only with its arguments and stored fields. Calls to output methods like `io.Writer.Write` should be synchronized unless -it can be verified that no locking is needed. Beware of facile claims like -"Unix writes are atomic"; the situation is a lot more nuanced than that. +it can be verified that no locking is needed. +As we saw in our example, storing a pointer to a mutex enables a logger and +all of its clones to synchronize with each other. +Beware of facile claims like "Unix writes are atomic"; the situation is a lot more nuanced than that. Some handlers have legitimate reasons for keeping state. For example, a handler might support a `SetLevel` method to change its configured level From b766721567ce15e3228b59ac1dd271e5fec9b2c0 Mon Sep 17 00:00:00 2001 From: Jonathan Amsterdam Date: Wed, 26 Jul 2023 06:19:39 -0400 Subject: [PATCH 15/48] slog-handler-guide: add testing section View at https://github.com/jba/markdown#testing. Change-Id: I0d8996395b03c3d1c7b7e50cce323a331fd614e1 Reviewed-on: https://go-review.googlesource.com/c/example/+/513137 TryBot-Result: Gopher Robot Reviewed-by: Ian Cottrell Run-TryBot: Jonathan Amsterdam --- go.mod | 2 + go.sum | 3 + slog-handler-guide/README.md | 59 ++++++++++- slog-handler-guide/guide.md | 35 +++++- .../indenthandler3/indent_handler_test.go | 100 +++++------------- 5 files changed, 118 insertions(+), 81 deletions(-) diff --git a/go.mod b/go.mod index 80b7ee1a..1996732f 100644 --- a/go.mod +++ b/go.mod @@ -3,3 +3,5 @@ module golang.org/x/example go 1.18 require golang.org/x/tools v0.0.0-20210112183307-1e6ecd4bf1b0 + +require gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 18a9ad7a..114d0dcc 100644 --- a/go.sum +++ b/go.sum @@ -22,3 +22,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/slog-handler-guide/README.md b/slog-handler-guide/README.md index 45d0f8b8..0dbb3404 100644 --- a/slog-handler-guide/README.md +++ b/slog-handler-guide/README.md @@ -706,15 +706,68 @@ Here there are no record attributes, so no group to open. ## Testing +The [`Handler` contract](https://pkg.go.dev/log/slog#Handler) specifies several +constraints on handlers. To verify that your handler follows these rules and generally produces proper -output, use the [testing/slogtest package](https://pkg.go.dev/log/slog). +output, use the [testing/slogtest package](https://pkg.go.dev/testing/slogtest). -TODO(jba): show the test function. +That package's `TestHandler` function takes an instance of your handler and +a function that returns its output formatted as a slice of maps. Here is the test function +for our example handler: -TODO(jba): reintroduce the material on Record.Clone that used to be here. +``` +func TestSlogtest(t *testing.T) { + var buf bytes.Buffer + err := slogtest.TestHandler(New(&buf, nil), func() []map[string]any { + return parseLogEntries(t, buf.Bytes()) + }) + if err != nil { + t.Error(err) + } +} +``` + +Calling `TestHandler` is easy. The hard part is parsing the output. +`TestHandler` calls your handler multiple times, resulting in a sequence of log +entries. +It is your job to parse each entry into a `map[string]any`. +A group in an entry should appear as a nested map. + +If your handler outputs a standard format, you can use an existing parser. +For example, if your handler outputs one JSON object per line, then you +can split the output into lines and call `encoding/json.Unmarshal` on each. +Parsers for other formats that can unmarshal into a map can be used out +of the box. +Our example output is enough like YAML so that we can use the `gopkg.in/yaml.v3` +package to parse it: + +``` +func parseLogEntries(t *testing.T, data []byte) []map[string]any { + entries := bytes.Split(data, []byte("---\n")) + entries = entries[:len(entries)-1] // last one is empty + var ms []map[string]any + for _, e := range entries { + var m map[string]any + if err := yaml.Unmarshal([]byte(e), &m); err != nil { + t.Fatal(err) + } + ms = append(ms, m) + } + return ms +} +``` + +If you have to write your own parser, it can be far from perfect. +The `slogtest` package uses only a handful of simple attributes. +(It is testing handler conformance, not parsing.) +Your parser can ignore edge cases like whitespace and newlines in keys and +values. Before switching to a YAML parser, we wrote an adequate custom parser +in 65 lines. # General considerations +TODO(jba): reintroduce the material on Record.Clone that used to be here. + ## Concurrency safety A handler must work properly when a single `Logger` is shared among several diff --git a/slog-handler-guide/guide.md b/slog-handler-guide/guide.md index 90554f5a..578236b0 100644 --- a/slog-handler-guide/guide.md +++ b/slog-handler-guide/guide.md @@ -449,15 +449,44 @@ Here there are no record attributes, so no group to open. ## Testing +The [`Handler` contract](https://pkg.go.dev/log/slog#Handler) specifies several +constraints on handlers. To verify that your handler follows these rules and generally produces proper -output, use the [testing/slogtest package](https://pkg.go.dev/log/slog). +output, use the [testing/slogtest package](https://pkg.go.dev/testing/slogtest). -TODO(jba): show the test function. +That package's `TestHandler` function takes an instance of your handler and +a function that returns its output formatted as a slice of maps. Here is the test function +for our example handler: -TODO(jba): reintroduce the material on Record.Clone that used to be here. +%include indenthandler3/indent_handler_test.go TestSlogtest - + +Calling `TestHandler` is easy. The hard part is parsing the output. +`TestHandler` calls your handler multiple times, resulting in a sequence of log +entries. +It is your job to parse each entry into a `map[string]any`. +A group in an entry should appear as a nested map. + +If your handler outputs a standard format, you can use an existing parser. +For example, if your handler outputs one JSON object per line, then you +can split the output into lines and call `encoding/json.Unmarshal` on each. +Parsers for other formats that can unmarshal into a map can be used out +of the box. +Our example output is enough like YAML so that we can use the `gopkg.in/yaml.v3` +package to parse it: + +%include indenthandler3/indent_handler_test.go parseLogEntries - + +If you have to write your own parser, it can be far from perfect. +The `slogtest` package uses only a handful of simple attributes. +(It is testing handler conformance, not parsing.) +Your parser can ignore edge cases like whitespace and newlines in keys and +values. Before switching to a YAML parser, we wrote an adequate custom parser +in 65 lines. # General considerations +TODO(jba): reintroduce the material on Record.Clone that used to be here. + ## Concurrency safety A handler must work properly when a single `Logger` is shared among several diff --git a/slog-handler-guide/indenthandler3/indent_handler_test.go b/slog-handler-guide/indenthandler3/indent_handler_test.go index f67bd032..8f6e99ca 100644 --- a/slog-handler-guide/indenthandler3/indent_handler_test.go +++ b/slog-handler-guide/indenthandler3/indent_handler_test.go @@ -3,30 +3,29 @@ package indenthandler import ( - "bufio" "bytes" - "fmt" + "log/slog" "reflect" "regexp" - "strconv" - "strings" "testing" "testing/slogtest" - "unicode" - "log/slog" + "gopkg.in/yaml.v3" ) +// !+TestSlogtest func TestSlogtest(t *testing.T) { var buf bytes.Buffer err := slogtest.TestHandler(New(&buf, nil), func() []map[string]any { - return parseLogEntries(buf.String()) + return parseLogEntries(t, buf.Bytes()) }) if err != nil { t.Error(err) } } +// !-TestSlogtest + func Test(t *testing.T) { var buf bytes.Buffer l := slog.New(New(&buf, nil)) @@ -56,71 +55,22 @@ d: "NO" } } -func parseLogEntries(s string) []map[string]any { +// !+parseLogEntries +func parseLogEntries(t *testing.T, data []byte) []map[string]any { + entries := bytes.Split(data, []byte("---\n")) + entries = entries[:len(entries)-1] // last one is empty var ms []map[string]any - scan := bufio.NewScanner(strings.NewReader(s)) - for scan.Scan() { - m := parseGroup(scan) + for _, e := range entries { + var m map[string]any + if err := yaml.Unmarshal([]byte(e), &m); err != nil { + t.Fatal(err) + } ms = append(ms, m) } - if scan.Err() != nil { - panic(scan.Err()) - } return ms } -func parseGroup(scan *bufio.Scanner) map[string]any { - m := map[string]any{} - groupIndent := -1 - for { - line := scan.Text() - if line == "---" { // end of entry - break - } - k, v, found := strings.Cut(line, ":") - if !found { - panic(fmt.Sprintf("no ':' in line %q", line)) - } - indent := strings.IndexFunc(k, func(r rune) bool { - return !unicode.IsSpace(r) - }) - if indent < 0 { - panic("blank line") - } - if groupIndent < 0 { - // First line in group; remember the indent. - groupIndent = indent - } else if indent < groupIndent { - // End of group - break - } else if indent > groupIndent { - panic(fmt.Sprintf("indent increased on line %q", line)) - } - - key := strings.TrimSpace(k) - if v == "" { - // Just a key: start of a group. - if !scan.Scan() { - panic("empty group") - } - m[key] = parseGroup(scan) - } else { - v = strings.TrimSpace(v) - if len(v) > 0 && v[0] == '"' { - var err error - v, err = strconv.Unquote(v) - if err != nil { - panic(err) - } - } - m[key] = v - if !scan.Scan() { - break - } - } - } - return m -} +// !-parseLogEntries func TestParseLogEntries(t *testing.T) { in := ` @@ -129,7 +79,7 @@ b: 2 c: 3 g: h: 4 - i: 5 + i: five d: 6 --- e: 7 @@ -137,20 +87,20 @@ e: 7 ` want := []map[string]any{ { - "a": "1", - "b": "2", - "c": "3", + "a": 1, + "b": 2, + "c": 3, "g": map[string]any{ - "h": "4", - "i": "5", + "h": 4, + "i": "five", }, - "d": "6", + "d": 6, }, { - "e": "7", + "e": 7, }, } - got := parseLogEntries(in[1:]) + got := parseLogEntries(t, []byte(in[1:])) if !reflect.DeepEqual(got, want) { t.Errorf("\ngot:\n%v\nwant:\n%v", got, want) } From 6088d5b0e079a42825ad0b2c98bbed1c06ffb10f Mon Sep 17 00:00:00 2001 From: Jonathan Amsterdam Date: Wed, 26 Jul 2023 07:33:34 -0400 Subject: [PATCH 16/48] slog-handler-guide: discuss Record.Clone Change-Id: I55c6367da953b9a59c4d5cd8ca817d3c051e12de Reviewed-on: https://go-review.googlesource.com/c/example/+/513138 TryBot-Result: Gopher Robot Reviewed-by: Ian Cottrell Run-TryBot: Jonathan Amsterdam --- slog-handler-guide/README.md | 40 +++++++++++++++++++++++++++++++++++- slog-handler-guide/guide.md | 39 ++++++++++++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/slog-handler-guide/README.md b/slog-handler-guide/README.md index 0dbb3404..cadc5017 100644 --- a/slog-handler-guide/README.md +++ b/slog-handler-guide/README.md @@ -16,6 +16,7 @@ This document is maintained by Jonathan Amsterdam `jba@google.com`. 1. [The `WithGroup` method](#the-`withgroup`-method) 1. [Testing](#testing) 1. [General considerations](#general-considerations) + 1. [Copying records](#copying-records) 1. [Concurrency safety](#concurrency-safety) 1. [Robustness](#robustness) 1. [Speed](#speed) @@ -766,7 +767,44 @@ in 65 lines. # General considerations -TODO(jba): reintroduce the material on Record.Clone that used to be here. +## Copying records + +Most handlers won't need to copy the `slog.Record` that is passed +to the `Handle` method. +Those that do must take special care in some cases. + +A handler can make a single copy of a `Record` with an ordinary Go +assignment, channel send or function call if it doesn't retain the +original. +But if its actions result in more than one copy, it should call `Record.Clone` +to make the copies so that they don't share state. +This `Handle` method passes the record to a single handler, so it doesn't require `Clone`: + + type Handler1 struct { + h slog.Handler + // ... + } + + func (h *Handler1) Handle(ctx context.Context, r slog.Record) error { + return h.h.Handle(ctx, r) + } + +This `Handle` method might pass the record to more than one handler, so it +should use `Clone`: + + type Handler2 struct { + hs []slog.Handler + // ... + } + + func (h *Handler2) Handle(ctx context.Context, r slog.Record) error { + for _, hh := range h.hs { + if err := hh.Handle(ctx, r.Clone()); err != nil { + return err + } + } + return nil + } ## Concurrency safety diff --git a/slog-handler-guide/guide.md b/slog-handler-guide/guide.md index 578236b0..229589b1 100644 --- a/slog-handler-guide/guide.md +++ b/slog-handler-guide/guide.md @@ -485,7 +485,44 @@ in 65 lines. # General considerations -TODO(jba): reintroduce the material on Record.Clone that used to be here. +## Copying records + +Most handlers won't need to copy the `slog.Record` that is passed +to the `Handle` method. +Those that do must take special care in some cases. + +A handler can make a single copy of a `Record` with an ordinary Go +assignment, channel send or function call if it doesn't retain the +original. +But if its actions result in more than one copy, it should call `Record.Clone` +to make the copies so that they don't share state. +This `Handle` method passes the record to a single handler, so it doesn't require `Clone`: + + type Handler1 struct { + h slog.Handler + // ... + } + + func (h *Handler1) Handle(ctx context.Context, r slog.Record) error { + return h.h.Handle(ctx, r) + } + +This `Handle` method might pass the record to more than one handler, so it +should use `Clone`: + + type Handler2 struct { + hs []slog.Handler + // ... + } + + func (h *Handler2) Handle(ctx context.Context, r slog.Record) error { + for _, hh := range h.hs { + if err := hh.Handle(ctx, r.Clone()); err != nil { + return err + } + } + return nil + } ## Concurrency safety From 00c7068f9d83018e9690baba399d3a7a61560bb1 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Fri, 28 Jul 2023 14:39:23 -0400 Subject: [PATCH 17/48] all: update to Go license For reasons that are lost to history, this repo was not using the standard Go license even though it is part of the Go project. Fix that, relicensing the code under the standard Go license and patent grant. Change-Id: I4b59aa0e20339afdf291b2303b0648ca953520fe Reviewed-on: https://go-review.googlesource.com/c/example/+/513995 TryBot-Result: Gopher Robot Auto-Submit: Russ Cox Run-TryBot: Russ Cox Reviewed-by: Cameron Balahan --- LICENSE | 229 ++++-------------------------- PATENTS | 22 +++ appengine-hello/app.go | 16 +-- appengine-hello/app.yaml | 15 +- appengine-hello/static/index.html | 16 +-- appengine-hello/static/script.js | 16 +-- appengine-hello/static/style.css | 16 +-- hello/hello.go | 18 +-- outyet/main.go | 20 +-- outyet/main_test.go | 18 +-- stringutil/reverse.go | 18 +-- stringutil/reverse_test.go | 18 +-- template/main.go | 20 +-- 13 files changed, 84 insertions(+), 358 deletions(-) create mode 100644 PATENTS diff --git a/LICENSE b/LICENSE index d6456956..6a66aea5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,202 +1,27 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/PATENTS b/PATENTS new file mode 100644 index 00000000..73309904 --- /dev/null +++ b/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/appengine-hello/app.go b/appengine-hello/app.go index 49b3a4f1..2951fe77 100644 --- a/appengine-hello/app.go +++ b/appengine-hello/app.go @@ -1,16 +1,6 @@ -// Copyright 2015 Google Inc. -// -// 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. +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. // Package hello is a simple App Engine application that replies to requests // on /hello with a welcoming message. diff --git a/appengine-hello/app.yaml b/appengine-hello/app.yaml index f8fa15c2..cde8839a 100644 --- a/appengine-hello/app.yaml +++ b/appengine-hello/app.yaml @@ -1,15 +1,6 @@ -# Copyright 2015 Google Inc. All rights reserved. -# 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 writing, software distributed -# under the License is distributed on a "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. +# Copyright 2023 The Go Authors. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. application: you-application-id version: 1 diff --git a/appengine-hello/static/index.html b/appengine-hello/static/index.html index 612d2b06..990eefe2 100644 --- a/appengine-hello/static/index.html +++ b/appengine-hello/static/index.html @@ -1,19 +1,9 @@ diff --git a/appengine-hello/static/script.js b/appengine-hello/static/script.js index 7bf47578..81e5df1b 100644 --- a/appengine-hello/static/script.js +++ b/appengine-hello/static/script.js @@ -1,17 +1,7 @@ /* -Copyright 2015 Google Inc. - -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. +Copyright 2023 The Go Authors. All rights reserved. +Use of this source code is governed by a BSD-style +license that can be found in the LICENSE file. */ "use strict"; diff --git a/appengine-hello/static/style.css b/appengine-hello/static/style.css index 9169ad58..2e283096 100644 --- a/appengine-hello/static/style.css +++ b/appengine-hello/static/style.css @@ -1,17 +1,7 @@ /* -Copyright 2015 Google Inc. - -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. +Copyright 2023 The Go Authors. All rights reserved. +Use of this source code is governed by a BSD-style +license that can be found in the LICENSE file. */ h1 { diff --git a/hello/hello.go b/hello/hello.go index e76ed81b..38802d29 100644 --- a/hello/hello.go +++ b/hello/hello.go @@ -1,18 +1,6 @@ -/* -Copyright 2014 Google Inc. - -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. -*/ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. package main diff --git a/outyet/main.go b/outyet/main.go index 96f77673..ab251baa 100644 --- a/outyet/main.go +++ b/outyet/main.go @@ -1,20 +1,8 @@ -/* -Copyright 2014 Google Inc. +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. -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. -*/ - -// outyet is a web server that announces whether or not a particular Go version +// Outyet is a web server that announces whether or not a particular Go version // has been tagged. package main diff --git a/outyet/main_test.go b/outyet/main_test.go index d21a22ce..894148ee 100644 --- a/outyet/main_test.go +++ b/outyet/main_test.go @@ -1,18 +1,6 @@ -/* -Copyright 2014 Google Inc. - -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. -*/ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. package main diff --git a/stringutil/reverse.go b/stringutil/reverse.go index d0bfd614..c7b24634 100644 --- a/stringutil/reverse.go +++ b/stringutil/reverse.go @@ -1,18 +1,6 @@ -/* -Copyright 2014 Google Inc. - -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. -*/ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. // Package stringutil contains utility functions for working with strings. package stringutil diff --git a/stringutil/reverse_test.go b/stringutil/reverse_test.go index 3a2337f1..9dbe42f2 100644 --- a/stringutil/reverse_test.go +++ b/stringutil/reverse_test.go @@ -1,18 +1,6 @@ -/* -Copyright 2014 Google Inc. - -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. -*/ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. package stringutil diff --git a/template/main.go b/template/main.go index 5da54752..b329b4ee 100644 --- a/template/main.go +++ b/template/main.go @@ -1,20 +1,8 @@ -/* -Copyright 2016 Google Inc. +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. -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. -*/ - -// Command template is a trivial web server that uses the text/template (and +// Template is a trivial web server that uses the text/template (and // html/template) package's "block" feature to implement a kind of template // inheritance. // From 1bcfdd08c5584d89507e37be70af63c72eb8c16f Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Fri, 28 Jul 2023 15:05:22 -0400 Subject: [PATCH 18/48] hello: create new standalone module example We want to revive golang.org/x/example as a test case for an experiment with a 'gonew' command. This CL changes the hello world program to be standalone and demonstrate a bit more about flag parsing and command-line logging. Change-Id: Iee481344c801f046813806a537d59e3242f9152a Reviewed-on: https://go-review.googlesource.com/c/example/+/513996 Reviewed-by: Cameron Balahan Run-TryBot: Russ Cox TryBot-Result: Gopher Robot Auto-Submit: Russ Cox --- README.md | 14 +++-- hello/go.mod | 4 ++ hello/hello.go | 61 ++++++++++++++++++- {stringutil => hello/reverse}/reverse.go | 8 +-- {stringutil => hello/reverse}/reverse_test.go | 8 +-- 5 files changed, 80 insertions(+), 15 deletions(-) create mode 100644 hello/go.mod rename {stringutil => hello/reverse}/reverse.go (58%) rename {stringutil => hello/reverse}/reverse_test.go (72%) diff --git a/README.md b/README.md index 2dad8742..253a18f0 100644 --- a/README.md +++ b/README.md @@ -13,21 +13,25 @@ $ cd example ``` https://go.googlesource.com/example is the canonical Git repository. It is mirrored at https://github.com/golang/example. -## [hello](hello/) and [stringutil](stringutil/) + +## [hello](hello/) and [hello/reverse](hello/reverse/) ``` $ cd hello $ go build +$ ./hello -help ``` -A trivial "Hello, world" program that uses a stringutil package. +A trivial "Hello, world" program that uses a library package. -Command [hello](hello/) covers: +The [hello](hello/) command covers: * The basic form of an executable command * Importing packages (from the standard library and the local repository) * Printing strings ([fmt](//golang.org/pkg/fmt/)) +* Command-line flags ([flag](//golang.org/pkg/flag/)) +* Logging ([log](//golang.org/pkg/log/)) -Library [stringutil](stringutil/) covers: +The [reverse](hello/reverse/) reverse covers: * The basic form of a library * Conversion between string and []rune @@ -37,7 +41,7 @@ Library [stringutil](stringutil/) covers: ``` $ cd outyet -$ go build +$ go run . ``` A web server that answers the question: "Is Go 1.x out yet?" diff --git a/hello/go.mod b/hello/go.mod new file mode 100644 index 00000000..eb0dea62 --- /dev/null +++ b/hello/go.mod @@ -0,0 +1,4 @@ +module golang.org/x/example/hello + +go 1.19 + diff --git a/hello/hello.go b/hello/hello.go index 38802d29..ce2a4234 100644 --- a/hello/hello.go +++ b/hello/hello.go @@ -2,14 +2,71 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// Hello is a hello, world program, demonstrating +// how to write a simple command-line program. +// +// Usage: +// +// hello [options] [name] +// +// The options are: +// +// -g greeting +// Greet with the given greeting, instead of "Hello". +// +// -r +// Greet in reverse. +// +// By default, hello greets the world. +// If a name is specified, hello greets that name instead. package main import ( + "flag" "fmt" + "log" + "os" - "golang.org/x/example/stringutil" + "golang.org/x/example/hello/reverse" +) + +func usage() { + fmt.Fprintf(os.Stderr, "usage: hello [options] [name]\n") + flag.PrintDefaults() + os.Exit(2) +} + +var ( + greeting = flag.String("g", "Hello", "Greet with `greeting`") + reverseFlag = flag.Bool("r", false, "Greet in reverse") ) func main() { - fmt.Println(stringutil.Reverse("!selpmaxe oG ,olleH")) + // Configure logging for a command-line program. + log.SetFlags(0) + log.SetPrefix("hello: ") + + // Parse flags. + flag.Usage = usage + flag.Parse() + + // Parse and validate arguments. + name := "world" + args := flag.Args() + if len(args) >= 2 { + usage() + } + if len(args) >= 1 { + name = args[0] + } + if name == "" { // hello '' is an error + log.Fatalf("invalid name %q", name) + } + + // Run actual logic. + if *reverseFlag { + fmt.Printf("%s, %s!\n", reverse.String(*greeting), reverse.String(name)) + return + } + fmt.Printf("%s, %s!\n", *greeting, name) } diff --git a/stringutil/reverse.go b/hello/reverse/reverse.go similarity index 58% rename from stringutil/reverse.go rename to hello/reverse/reverse.go index c7b24634..b6fe74a6 100644 --- a/stringutil/reverse.go +++ b/hello/reverse/reverse.go @@ -2,11 +2,11 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package stringutil contains utility functions for working with strings. -package stringutil +// Package reverse can reverse things, particularly strings. +package reverse -// Reverse returns its argument string reversed rune-wise left to right. -func Reverse(s string) string { +// String returns its argument string reversed rune-wise left to right. +func String(s string) string { r := []rune(s) for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 { r[i], r[j] = r[j], r[i] diff --git a/stringutil/reverse_test.go b/hello/reverse/reverse_test.go similarity index 72% rename from stringutil/reverse_test.go rename to hello/reverse/reverse_test.go index 9dbe42f2..d1301177 100644 --- a/stringutil/reverse_test.go +++ b/hello/reverse/reverse_test.go @@ -2,11 +2,11 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package stringutil +package reverse import "testing" -func TestReverse(t *testing.T) { +func TestString(t *testing.T) { for _, c := range []struct { in, want string }{ @@ -14,9 +14,9 @@ func TestReverse(t *testing.T) { {"Hello, 世界", "界世 ,olleH"}, {"", ""}, } { - got := Reverse(c.in) + got := String(c.in) if got != c.want { - t.Errorf("Reverse(%q) == %q, want %q", c.in, got, c.want) + t.Errorf("String(%q) == %q, want %q", c.in, got, c.want) } } } From 3dc24130247597153c0c54156448d48a260b4a07 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Fri, 28 Jul 2023 15:06:57 -0400 Subject: [PATCH 19/48] helloserver: add basic hello world web server We want to revive golang.org/x/example as a test case for an experiment with a 'gonew' command. This CL adds a basic "hello, world" web server as golang.org/x/example/helloserver. Change-Id: Icf76c756f7b256285d4c5e6a33655996597eb2da Reviewed-on: https://go-review.googlesource.com/c/example/+/513997 Auto-Submit: Russ Cox TryBot-Result: Gopher Robot Run-TryBot: Russ Cox Reviewed-by: Cameron Balahan --- helloserver/go.mod | 4 +++ helloserver/server.go | 75 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 helloserver/go.mod create mode 100644 helloserver/server.go diff --git a/helloserver/go.mod b/helloserver/go.mod new file mode 100644 index 00000000..1de477a1 --- /dev/null +++ b/helloserver/go.mod @@ -0,0 +1,4 @@ +module golang.org/x/example/helloserver + +go 1.19 + diff --git a/helloserver/server.go b/helloserver/server.go new file mode 100644 index 00000000..425386a5 --- /dev/null +++ b/helloserver/server.go @@ -0,0 +1,75 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Hello is a simple hello, world demonstration web server. +// +// It serves version information on /version and answers +// any other request like /name by saying "Hello, name!". +// +// See golang.org/x/example/outyet for a more sophisticated server. +package main + +import ( + "flag" + "fmt" + "html" + "log" + "net/http" + "os" + "runtime/debug" + "strings" +) + +func usage() { + fmt.Fprintf(os.Stderr, "usage: helloserver [options]\n") + flag.PrintDefaults() + os.Exit(2) +} + +var ( + greeting = flag.String("g", "Hello", "Greet with `greeting`") + addr = flag.String("addr", "localhost:8080", "address to serve") +) + +func main() { + // Parse flags. + flag.Usage = usage + flag.Parse() + + // Parse and validate arguments (none). + args := flag.Args() + if len(args) != 0 { + usage() + } + + // Register handlers. + // All requests not otherwise mapped with go to greet. + // /version is mapped specifically to version. + http.HandleFunc("/", greet) + http.HandleFunc("/version", version) + + log.Printf("serving http://%s\n", *addr) + log.Fatal(http.ListenAndServe(*addr, nil)) +} + +func version(w http.ResponseWriter, r *http.Request) { + info, ok := debug.ReadBuildInfo() + if !ok { + http.Error(w, "no build information available", 500) + return + } + + fmt.Fprintf(w, "\n
\n")
+	fmt.Fprintf(w, "%s\n", html.EscapeString(info.String()))
+}
+
+func greet(w http.ResponseWriter, r *http.Request) {
+	name := strings.Trim(r.URL.Path, "/")
+	if name == "" {
+		name = "Gopher"
+	}
+
+	fmt.Fprintf(w, "\n")
+	fmt.Fprintf(w, "Hello, %s!\n", html.EscapeString(name))
+}

From 0c9b5c54f5011f0de364af4aaa45d57d49090d94 Mon Sep 17 00:00:00 2001
From: Russ Cox 
Date: Fri, 28 Jul 2023 15:08:28 -0400
Subject: [PATCH 20/48] outyet: update example, make a separate module

We want to revive golang.org/x/example as a test case for
an experiment with a 'gonew' command.

This CL updates outyet to be a gonew-able sample server,
by making it its own module. The CL also removes
Dockerfile and containers.yaml because I think the
best practices around those files have evolved too much
since 2015 when they were written.

Change-Id: I53faf4b30de9e4ef3bfe35a781732daa140a9877
Reviewed-on: https://go-review.googlesource.com/c/example/+/513998
TryBot-Result: Gopher Robot 
Run-TryBot: Russ Cox 
Auto-Submit: Russ Cox 
Reviewed-by: Cameron Balahan 
---
 README.md              | 15 +++++++++++++++
 outyet/Dockerfile      |  2 --
 outyet/containers.yaml |  8 --------
 outyet/go.mod          |  4 ++++
 outyet/main.go         |  3 ++-
 5 files changed, 21 insertions(+), 11 deletions(-)
 delete mode 100644 outyet/Dockerfile
 delete mode 100644 outyet/containers.yaml
 create mode 100644 outyet/go.mod

diff --git a/README.md b/README.md
index 253a18f0..88aa3ed2 100644
--- a/README.md
+++ b/README.md
@@ -37,6 +37,21 @@ The [reverse](hello/reverse/) reverse covers:
 * Conversion between string and []rune
 * Table-driven unit tests ([testing](//golang.org/pkg/testing/))
 
+## [helloserver](helloserver/)
+
+```
+$ cd helloserver
+$ go run .
+```
+
+A trivial "Hello, world" web server.
+
+Topics covered:
+
+* Command-line flags ([flag](//golang.org/pkg/flag/))
+* Logging ([log](//golang.org/pkg/log/))
+* Web servers ([net/http](//golang.org/pkg/net/http/))
+
 ## [outyet](outyet/)
 
 ```
diff --git a/outyet/Dockerfile b/outyet/Dockerfile
deleted file mode 100644
index 3fa58ff7..00000000
--- a/outyet/Dockerfile
+++ /dev/null
@@ -1,2 +0,0 @@
-FROM golang:onbuild
-EXPOSE 8080
diff --git a/outyet/containers.yaml b/outyet/containers.yaml
deleted file mode 100644
index 44ba78a6..00000000
--- a/outyet/containers.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
-version: v1beta2
-containers:
-- name: outyet
-  image: adg1/outyet
-  ports:
-  - name: http
-    hostPort: 80
-    containerPort: 8080
diff --git a/outyet/go.mod b/outyet/go.mod
new file mode 100644
index 00000000..720bbb4d
--- /dev/null
+++ b/outyet/go.mod
@@ -0,0 +1,4 @@
+module golang.org/x/example/outyet
+
+go 1.19
+
diff --git a/outyet/main.go b/outyet/main.go
index ab251baa..496af2c1 100644
--- a/outyet/main.go
+++ b/outyet/main.go
@@ -19,7 +19,7 @@ import (
 
 // Command-line flags.
 var (
-	httpAddr   = flag.String("http", ":8080", "Listen address")
+	httpAddr   = flag.String("http", "localhost:8080", "Listen address")
 	pollPeriod = flag.Duration("poll", 5*time.Second, "Poll period")
 	version    = flag.String("version", "1.4", "Go version")
 )
@@ -30,6 +30,7 @@ func main() {
 	flag.Parse()
 	changeURL := fmt.Sprintf("%sgo%s", baseChangeURL, *version)
 	http.Handle("/", NewServer(*version, changeURL, *pollPeriod))
+	log.Printf("serving http://%s", *httpAddr)
 	log.Fatal(http.ListenAndServe(*httpAddr, nil))
 }
 

From 19299f6048bb8ab82de0cf9266ae46356c3b12b7 Mon Sep 17 00:00:00 2001
From: Russ Cox 
Date: Mon, 31 Jul 2023 17:12:23 -0400
Subject: [PATCH 21/48] helloserver: implement -g flag

It was documented and parsed but not used.

Change-Id: I4c19429b3761819b3bae70f06fc2b55431c0f88c
Reviewed-on: https://go-review.googlesource.com/c/example/+/514578
TryBot-Result: Gopher Robot 
Run-TryBot: Russ Cox 
Reviewed-by: Ian Lance Taylor 
Auto-Submit: Russ Cox 
---
 helloserver/server.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/helloserver/server.go b/helloserver/server.go
index 425386a5..ea3977eb 100644
--- a/helloserver/server.go
+++ b/helloserver/server.go
@@ -71,5 +71,5 @@ func greet(w http.ResponseWriter, r *http.Request) {
 	}
 
 	fmt.Fprintf(w, "\n")
-	fmt.Fprintf(w, "Hello, %s!\n", html.EscapeString(name))
+	fmt.Fprintf(w, "%s, %s!\n", *greeting, html.EscapeString(name))
 }

From 17e2836bb62e8742ac5fada9d2b4209d45168dca Mon Sep 17 00:00:00 2001
From: Russ Cox 
Date: Wed, 2 Aug 2023 17:39:07 -0400
Subject: [PATCH 22/48] hello/reverse: add example example test

This will be linked from an updated go.dev/blog/examples.

For #61722.

Change-Id: I329141141e0590a6d7ee0b6b504c8f7bdc9c2d5f
Reviewed-on: https://go-review.googlesource.com/c/example/+/515237
TryBot-Result: Gopher Robot 
Run-TryBot: Russ Cox 
Reviewed-by: Hyang-Ah Hana Kim 
---
 hello/reverse/example_test.go | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)
 create mode 100644 hello/reverse/example_test.go

diff --git a/hello/reverse/example_test.go b/hello/reverse/example_test.go
new file mode 100644
index 00000000..a0df581a
--- /dev/null
+++ b/hello/reverse/example_test.go
@@ -0,0 +1,16 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package reverse_test
+
+import (
+	"fmt"
+
+	"golang.org/x/example/hello/reverse"
+)
+
+func ExampleString() {
+	fmt.Println(reverse.String("hello"))
+	// Output: olleh
+}

From 344698fc2aecde701fe14f7937775a7ce2eee8e8 Mon Sep 17 00:00:00 2001
From: Jonathan Amsterdam 
Date: Wed, 26 Jul 2023 15:20:00 -0400
Subject: [PATCH 23/48] slog-handler-guide: speed section

Change-Id: If06f51747e6e4926d95e88840ab5fdce5f9a0189
Reviewed-on: https://go-review.googlesource.com/c/example/+/513457
Reviewed-by: Ian Cottrell 
TryBot-Result: Gopher Robot 
Run-TryBot: Jonathan Amsterdam 
---
 slog-handler-guide/README.md                  | 120 +++++++++-
 slog-handler-guide/guide.md                   | 102 ++++++++-
 .../indenthandler4/indent_handler.go          | 208 ++++++++++++++++++
 .../indent_handler_norace_test.go             |  46 ++++
 .../indenthandler4/indent_handler_test.go     | 107 +++++++++
 5 files changed, 578 insertions(+), 5 deletions(-)
 create mode 100644 slog-handler-guide/indenthandler4/indent_handler.go
 create mode 100644 slog-handler-guide/indenthandler4/indent_handler_norace_test.go
 create mode 100644 slog-handler-guide/indenthandler4/indent_handler_test.go

diff --git a/slog-handler-guide/README.md b/slog-handler-guide/README.md
index cadc5017..3172f0e8 100644
--- a/slog-handler-guide/README.md
+++ b/slog-handler-guide/README.md
@@ -894,6 +894,122 @@ error.
 
 ## Speed
 
-TODO(jba): discuss
+Most programs don't need fast logging.
+Before making your handler fast, gather data—preferably production data,
+not benchmark comparisons—that demonstrates that it needs to be fast.
+Avoid premature optimization.
 
-TODO(jba): show how to pool a []byte.
+If you need a fast handler, start with pre-formatting. It may provide dramatic
+speed-ups in cases where a single call to `Logger.With` is followed by many
+calls to the resulting logger.
+
+If log output is the bottleneck, consider making your handler asynchronous.
+Do the minimal amount of processing in the handler, then send the record and
+other information over a channel. Another goroutine can collect the incoming log
+entries and write them in bulk and in the background.
+You might want to preserve the option to log synchronously
+so you can see all the log output to debug a crash.
+
+Allocation is often a major cause of a slow system.
+The `slog` package already works hard at minimizing allocations.
+If your handler does it own allocation, and profiling shows it to be
+a problem, then see if you can minimize it.
+
+One simple change you can make is to replace calls to `fmt.Sprintf` or `fmt.Appendf`
+with direct appends to the buffer. For example, our IndentHandler appends string
+attributes to the buffer like so:
+
+	buf = fmt.Appendf(buf, "%s: %q\n", a.Key, a.Value.String())
+
+As of Go 1.21, that results in two allocations, one for each argument passed to
+an `any` parameter. We can get that down to zero by using `append` directly:
+
+	buf = append(buf, a.Key...)
+	buf = append(buf, ": "...)
+	buf = strconv.AppendQuote(buf, a.Value.String())
+	buf = append(buf, '\n')
+
+Another worthwhile change is to use a `sync.Pool` to manage the one chunk of
+memory that most handlers need:
+the `[]byte` buffer holding the formatted output.
+
+Our example `Handle` method began with this line:
+
+	buf := make([]byte, 0, 1024)
+
+As we said above, providing a large initial capacity avoids repeated copying and
+re-allocation of the slice as it grows, reducing the number of allocations to
+one.
+But we can get it down to zero in the steady state by keeping a global pool of buffers.
+Initially, the pool will be empty and new buffers will be allocated.
+But eventually, assuming the number of concurrent log calls reaches a steady
+maximum, there will be enough buffers in the pool to share among all the
+ongoing `Handler` calls. As long as no log entry grows past a buffer's capacity,
+there will be no allocations from the garbage collector's point of view.
+
+We will hide our pool behind a pair of functions, `allocBuf` and `freeBuf`.
+The single line to get a buffer at the top of `Handle` becomes two lines:
+
+	bufp := allocBuf()
+	defer freeBuf(bufp)
+
+One of the subtleties involved in making a `sync.Pool` of slices
+is suggested by the variable name `bufp`: your pool must deal in
+_pointers_ to slices, not the slices themselves.
+Pooled values must always be pointers. If they aren't, then the `any` arguments
+and return values of the `sync.Pool` methods will themselves cause allocations,
+defeating the purpose of pooling.
+
+There are two ways to proceed with our slice pointer: we can replace `buf`
+with `*bufp` throughout our function, or we can dereference it and remember to
+re-assign it before freeing:
+
+	bufp := allocBuf()
+	buf := *bufp
+	defer func() {
+		*bufp = buf
+		freeBuf(bufp)
+	}()
+
+
+Here is our pool and its associated functions:
+
+```
+var bufPool = sync.Pool{
+	New: func() any {
+		b := make([]byte, 0, 1024)
+		return &b
+	},
+}
+
+func allocBuf() *[]byte {
+	return bufPool.Get().(*[]byte)
+}
+
+func freeBuf(b *[]byte) {
+	// To reduce peak allocation, return only smaller buffers to the pool.
+	const maxBufferSize = 16 << 10
+	if cap(*b) <= maxBufferSize {
+		*b = (*b)[:0]
+		bufPool.Put(b)
+	}
+}
+```
+
+The pool's `New` function does the same thing as the original code:
+create a byte slice with 0 length and plenty of capacity.
+The `allocBuf` function just type-asserts the result of the pool's
+`Get` method.
+
+The `freeBuf` method truncates the buffer before putting it back
+in the pool, so that `allocBuf` always returns zero-length slices.
+It also implements an important optimization: it doesn't return
+large buffers to the pool.
+To see why this important, consider what would happen if there were a single,
+unusually large log entry—say one that was a megabyte when formatted.
+If that megabyte-sized buffer were put in the pool, it could remain
+there indefinitely, constantly being reused, but with most of its capacity
+wasted.
+The extra memory might never be used again by the handler, and since it was in
+the handler's pool, it might never be garbage-collected for reuse elsewhere.
+We can avoid that situation by keeping large buffers out of the pool.
diff --git a/slog-handler-guide/guide.md b/slog-handler-guide/guide.md
index 229589b1..92bcd261 100644
--- a/slog-handler-guide/guide.md
+++ b/slog-handler-guide/guide.md
@@ -612,6 +612,102 @@ error.
 
 ## Speed
 
-TODO(jba): discuss
-
-TODO(jba): show how to pool a []byte.
+Most programs don't need fast logging.
+Before making your handler fast, gather data—preferably production data,
+not benchmark comparisons—that demonstrates that it needs to be fast.
+Avoid premature optimization.
+
+If you need a fast handler, start with pre-formatting. It may provide dramatic
+speed-ups in cases where a single call to `Logger.With` is followed by many
+calls to the resulting logger.
+
+If log output is the bottleneck, consider making your handler asynchronous.
+Do the minimal amount of processing in the handler, then send the record and
+other information over a channel. Another goroutine can collect the incoming log
+entries and write them in bulk and in the background.
+You might want to preserve the option to log synchronously
+so you can see all the log output to debug a crash.
+
+Allocation is often a major cause of a slow system.
+The `slog` package already works hard at minimizing allocations.
+If your handler does its own allocation, and profiling shows it to be
+a problem, then see if you can minimize it.
+
+One simple change you can make is to replace calls to `fmt.Sprintf` or `fmt.Appendf`
+with direct appends to the buffer. For example, our IndentHandler appends string
+attributes to the buffer like so:
+
+	buf = fmt.Appendf(buf, "%s: %q\n", a.Key, a.Value.String())
+
+As of Go 1.21, that results in two allocations, one for each argument passed to
+an `any` parameter. We can get that down to zero by using `append` directly:
+
+	buf = append(buf, a.Key...)
+	buf = append(buf, ": "...)
+	buf = strconv.AppendQuote(buf, a.Value.String())
+	buf = append(buf, '\n')
+
+Another worthwhile change is to use a `sync.Pool` to manage the one chunk of
+memory that most handlers need:
+the `[]byte` buffer holding the formatted output.
+
+Our example `Handle` method began with this line:
+
+	buf := make([]byte, 0, 1024)
+
+As we said above, providing a large initial capacity avoids repeated copying and
+re-allocation of the slice as it grows, reducing the number of allocations to
+one.
+But we can get it down to zero in the steady state by keeping a global pool of buffers.
+Initially, the pool will be empty and new buffers will be allocated.
+But eventually, assuming the number of concurrent log calls reaches a steady
+maximum, there will be enough buffers in the pool to share among all the
+ongoing `Handler` calls. As long as no log entry grows past a buffer's capacity,
+there will be no allocations from the garbage collector's point of view.
+
+We will hide our pool behind a pair of functions, `allocBuf` and `freeBuf`.
+The single line to get a buffer at the top of `Handle` becomes two lines:
+
+	bufp := allocBuf()
+	defer freeBuf(bufp)
+
+One of the subtleties involved in making a `sync.Pool` of slices
+is suggested by the variable name `bufp`: your pool must deal in
+_pointers_ to slices, not the slices themselves.
+Pooled values must always be pointers. If they aren't, then the `any` arguments
+and return values of the `sync.Pool` methods will themselves cause allocations,
+defeating the purpose of pooling.
+
+There are two ways to proceed with our slice pointer: we can replace `buf`
+with `*bufp` throughout our function, or we can dereference it and remember to
+re-assign it before freeing:
+
+	bufp := allocBuf()
+	buf := *bufp
+	defer func() {
+		*bufp = buf
+		freeBuf(bufp)
+	}()
+
+
+Here is our pool and its associated functions:
+
+%include indenthandler4/indent_handler.go pool -
+
+The pool's `New` function does the same thing as the original code:
+create a byte slice with 0 length and plenty of capacity.
+The `allocBuf` function just type-asserts the result of the pool's
+`Get` method.
+
+The `freeBuf` method truncates the buffer before putting it back
+in the pool, so that `allocBuf` always returns zero-length slices.
+It also implements an important optimization: it doesn't return
+large buffers to the pool.
+To see why this important, consider what would happen if there were a single,
+unusually large log entry—say one that was a megabyte when formatted.
+If that megabyte-sized buffer were put in the pool, it could remain
+there indefinitely, constantly being reused, but with most of its capacity
+wasted.
+The extra memory might never be used again by the handler, and since it was in
+the handler's pool, it might never be garbage-collected for reuse elsewhere.
+We can avoid that situation by keeping large buffers out of the pool.
diff --git a/slog-handler-guide/indenthandler4/indent_handler.go b/slog-handler-guide/indenthandler4/indent_handler.go
new file mode 100644
index 00000000..bf96c5dc
--- /dev/null
+++ b/slog-handler-guide/indenthandler4/indent_handler.go
@@ -0,0 +1,208 @@
+//go:build go1.21
+
+package indenthandler
+
+import (
+	"context"
+	"fmt"
+	"io"
+	"log/slog"
+	"runtime"
+	"slices"
+	"strconv"
+	"sync"
+	"time"
+)
+
+// !+IndentHandler
+type IndentHandler struct {
+	opts           Options
+	preformatted   []byte   // data from WithGroup and WithAttrs
+	unopenedGroups []string // groups from WithGroup that haven't been opened
+	indentLevel    int      // same as number of opened groups so far
+	mu             *sync.Mutex
+	out            io.Writer
+}
+
+//!-IndentHandler
+
+type Options struct {
+	// Level reports the minimum level to log.
+	// Levels with lower levels are discarded.
+	// If nil, the Handler uses [slog.LevelInfo].
+	Level slog.Leveler
+}
+
+func New(out io.Writer, opts *Options) *IndentHandler {
+	h := &IndentHandler{out: out, mu: &sync.Mutex{}}
+	if opts != nil {
+		h.opts = *opts
+	}
+	if h.opts.Level == nil {
+		h.opts.Level = slog.LevelInfo
+	}
+	return h
+}
+
+func (h *IndentHandler) Enabled(ctx context.Context, level slog.Level) bool {
+	return level >= h.opts.Level.Level()
+}
+
+// !+WithGroup
+func (h *IndentHandler) WithGroup(name string) slog.Handler {
+	if name == "" {
+		return h
+	}
+	h2 := *h
+	// Add an unopened group to h2 without modifying h.
+	h2.unopenedGroups = make([]string, len(h.unopenedGroups)+1)
+	copy(h2.unopenedGroups, h.unopenedGroups)
+	h2.unopenedGroups[len(h2.unopenedGroups)-1] = name
+	return &h2
+}
+
+//!-WithGroup
+
+// !+WithAttrs
+func (h *IndentHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
+	if len(attrs) == 0 {
+		return h
+	}
+	h2 := *h
+	// Force an append to copy the underlying array.
+	pre := slices.Clip(h.preformatted)
+	// Add all groups from WithGroup that haven't already been added.
+	h2.preformatted = h2.appendUnopenedGroups(pre, h2.indentLevel)
+	// Each of those groups increased the indent level by 1.
+	h2.indentLevel += len(h2.unopenedGroups)
+	// Now all groups have been opened.
+	h2.unopenedGroups = nil
+	// Pre-format the attributes.
+	for _, a := range attrs {
+		h2.preformatted = h2.appendAttr(h2.preformatted, a, h2.indentLevel)
+	}
+	return &h2
+}
+
+func (h *IndentHandler) appendUnopenedGroups(buf []byte, indentLevel int) []byte {
+	for _, g := range h.unopenedGroups {
+		buf = fmt.Appendf(buf, "%*s%s:\n", indentLevel*4, "", g)
+		indentLevel++
+	}
+	return buf
+}
+
+//!-WithAttrs
+
+// !+Handle
+func (h *IndentHandler) Handle(ctx context.Context, r slog.Record) error {
+	bufp := allocBuf()
+	buf := *bufp
+	defer func() {
+		*bufp = buf
+		freeBuf(bufp)
+	}()
+	if !r.Time.IsZero() {
+		buf = h.appendAttr(buf, slog.Time(slog.TimeKey, r.Time), 0)
+	}
+	buf = h.appendAttr(buf, slog.Any(slog.LevelKey, r.Level), 0)
+	if r.PC != 0 {
+		fs := runtime.CallersFrames([]uintptr{r.PC})
+		f, _ := fs.Next()
+		// Optimize to minimize allocation.
+		srcbufp := allocBuf()
+		defer freeBuf(srcbufp)
+		*srcbufp = append(*srcbufp, f.File...)
+		*srcbufp = append(*srcbufp, ':')
+		*srcbufp = strconv.AppendInt(*srcbufp, int64(f.Line), 10)
+		buf = h.appendAttr(buf, slog.String(slog.SourceKey, string(*srcbufp)), 0)
+	}
+
+	buf = h.appendAttr(buf, slog.String(slog.MessageKey, r.Message), 0)
+	// Insert preformatted attributes just after built-in ones.
+	buf = append(buf, h.preformatted...)
+	if r.NumAttrs() > 0 {
+		buf = h.appendUnopenedGroups(buf, h.indentLevel)
+		r.Attrs(func(a slog.Attr) bool {
+			buf = h.appendAttr(buf, a, h.indentLevel+len(h.unopenedGroups))
+			return true
+		})
+	}
+	buf = append(buf, "---\n"...)
+	h.mu.Lock()
+	defer h.mu.Unlock()
+	_, err := h.out.Write(buf)
+	return err
+}
+
+//!-Handle
+
+func (h *IndentHandler) appendAttr(buf []byte, a slog.Attr, indentLevel int) []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
+	}
+	// Indent 4 spaces per level.
+	buf = fmt.Appendf(buf, "%*s", indentLevel*4, "")
+	switch a.Value.Kind() {
+	case slog.KindString:
+		// Quote string values, to make them easy to parse.
+		buf = append(buf, a.Key...)
+		buf = append(buf, ": "...)
+		buf = strconv.AppendQuote(buf, a.Value.String())
+		buf = append(buf, '\n')
+	case slog.KindTime:
+		// Write times in a standard way, without the monotonic time.
+		buf = append(buf, a.Key...)
+		buf = append(buf, ": "...)
+		buf = a.Value.Time().AppendFormat(buf, time.RFC3339Nano)
+		buf = append(buf, '\n')
+	case slog.KindGroup:
+		attrs := a.Value.Group()
+		// Ignore empty groups.
+		if len(attrs) == 0 {
+			return buf
+		}
+		// If the key is non-empty, write it out and indent the rest of the attrs.
+		// Otherwise, inline the attrs.
+		if a.Key != "" {
+			buf = fmt.Appendf(buf, "%s:\n", a.Key)
+			indentLevel++
+		}
+		for _, ga := range attrs {
+			buf = h.appendAttr(buf, ga, indentLevel)
+		}
+
+	default:
+		buf = append(buf, a.Key...)
+		buf = append(buf, ": "...)
+		buf = append(buf, a.Value.String()...)
+		buf = append(buf, '\n')
+	}
+	return buf
+}
+
+// !+pool
+var bufPool = sync.Pool{
+	New: func() any {
+		b := make([]byte, 0, 1024)
+		return &b
+	},
+}
+
+func allocBuf() *[]byte {
+	return bufPool.Get().(*[]byte)
+}
+
+func freeBuf(b *[]byte) {
+	// To reduce peak allocation, return only smaller buffers to the pool.
+	const maxBufferSize = 16 << 10
+	if cap(*b) <= maxBufferSize {
+		*b = (*b)[:0]
+		bufPool.Put(b)
+	}
+}
+
+//!-pool
diff --git a/slog-handler-guide/indenthandler4/indent_handler_norace_test.go b/slog-handler-guide/indenthandler4/indent_handler_norace_test.go
new file mode 100644
index 00000000..bddfd4d0
--- /dev/null
+++ b/slog-handler-guide/indenthandler4/indent_handler_norace_test.go
@@ -0,0 +1,46 @@
+//go:build go1.21 && !race
+
+package indenthandler
+
+import (
+	"fmt"
+	"io"
+	"log/slog"
+	"strconv"
+	"testing"
+)
+
+func TestAlloc(t *testing.T) {
+	a := slog.String("key", "value")
+	t.Run("Appendf", func(t *testing.T) {
+		buf := make([]byte, 0, 100)
+		g := testing.AllocsPerRun(2, func() {
+			buf = fmt.Appendf(buf, "%s: %q\n", a.Key, a.Value.String())
+		})
+		if g, w := int(g), 2; g != w {
+			t.Errorf("got %d, want %d", g, w)
+		}
+	})
+	t.Run("appends", func(t *testing.T) {
+		buf := make([]byte, 0, 100)
+		g := testing.AllocsPerRun(2, func() {
+			buf = append(buf, a.Key...)
+			buf = append(buf, ": "...)
+			buf = strconv.AppendQuote(buf, a.Value.String())
+			buf = append(buf, '\n')
+		})
+		if g, w := int(g), 0; g != w {
+			t.Errorf("got %d, want %d", g, w)
+		}
+	})
+
+	t.Run("Handle", func(t *testing.T) {
+		l := slog.New(New(io.Discard, nil))
+		got := testing.AllocsPerRun(10, func() {
+			l.LogAttrs(nil, slog.LevelInfo, "hello", slog.String("a", "1"))
+		})
+		if g, w := int(got), 6; g > w {
+			t.Errorf("got %d, want at most %d", g, w)
+		}
+	})
+}
diff --git a/slog-handler-guide/indenthandler4/indent_handler_test.go b/slog-handler-guide/indenthandler4/indent_handler_test.go
new file mode 100644
index 00000000..8f6e99ca
--- /dev/null
+++ b/slog-handler-guide/indenthandler4/indent_handler_test.go
@@ -0,0 +1,107 @@
+//go:build go1.21
+
+package indenthandler
+
+import (
+	"bytes"
+	"log/slog"
+	"reflect"
+	"regexp"
+	"testing"
+	"testing/slogtest"
+
+	"gopkg.in/yaml.v3"
+)
+
+// !+TestSlogtest
+func TestSlogtest(t *testing.T) {
+	var buf bytes.Buffer
+	err := slogtest.TestHandler(New(&buf, nil), func() []map[string]any {
+		return parseLogEntries(t, buf.Bytes())
+	})
+	if err != nil {
+		t.Error(err)
+	}
+}
+
+// !-TestSlogtest
+
+func Test(t *testing.T) {
+	var buf bytes.Buffer
+	l := slog.New(New(&buf, nil))
+	l.Info("hello", "a", 1, "b", true, "c", 3.14, slog.Group("g", "h", 1, "i", 2), "d", "NO")
+	got := buf.String()
+	wantre := `time: [-0-9T:.]+Z?
+level: INFO
+source: ".*/indent_handler_test.go:\d+"
+msg: "hello"
+a: 1
+b: true
+c: 3.14
+g:
+    h: 1
+    i: 2
+d: "NO"
+`
+	re := regexp.MustCompile(wantre)
+	if !re.MatchString(got) {
+		t.Errorf("\ngot:\n%q\nwant:\n%q", got, wantre)
+	}
+
+	buf.Reset()
+	l.Debug("test")
+	if got := buf.Len(); got != 0 {
+		t.Errorf("got buf.Len() = %d, want 0", got)
+	}
+}
+
+// !+parseLogEntries
+func parseLogEntries(t *testing.T, data []byte) []map[string]any {
+	entries := bytes.Split(data, []byte("---\n"))
+	entries = entries[:len(entries)-1] // last one is empty
+	var ms []map[string]any
+	for _, e := range entries {
+		var m map[string]any
+		if err := yaml.Unmarshal([]byte(e), &m); err != nil {
+			t.Fatal(err)
+		}
+		ms = append(ms, m)
+	}
+	return ms
+}
+
+// !-parseLogEntries
+
+func TestParseLogEntries(t *testing.T) {
+	in := `
+a: 1
+b: 2
+c: 3
+g:
+    h: 4
+    i: five
+d: 6
+---
+e: 7
+---
+`
+	want := []map[string]any{
+		{
+			"a": 1,
+			"b": 2,
+			"c": 3,
+			"g": map[string]any{
+				"h": 4,
+				"i": "five",
+			},
+			"d": 6,
+		},
+		{
+			"e": 7,
+		},
+	}
+	got := parseLogEntries(t, []byte(in[1:]))
+	if !reflect.DeepEqual(got, want) {
+		t.Errorf("\ngot:\n%v\nwant:\n%v", got, want)
+	}
+}

From a16ef21b42352073bb15eabb54450e7c84bdb034 Mon Sep 17 00:00:00 2001
From: Jonathan Amsterdam 
Date: Sat, 29 Jul 2023 08:00:29 -0400
Subject: [PATCH 24/48] slog-handler-guide: minor prose tweaks

Change-Id: Ie5299ec0f6e9d1936281152290edf397a0956b90
Reviewed-on: https://go-review.googlesource.com/c/example/+/514055
Run-TryBot: Jonathan Amsterdam 
Reviewed-by: Ian Cottrell 
TryBot-Result: Gopher Robot 
---
 slog-handler-guide/README.md | 18 ++++++++----------
 slog-handler-guide/guide.md  | 18 ++++++++----------
 2 files changed, 16 insertions(+), 20 deletions(-)

diff --git a/slog-handler-guide/README.md b/slog-handler-guide/README.md
index 3172f0e8..946f653d 100644
--- a/slog-handler-guide/README.md
+++ b/slog-handler-guide/README.md
@@ -44,7 +44,7 @@ such as gathering key-value pairs into `Attr`s, and then call one or more
 and the output methods.
 
 An output method fulfills the main role of a logger: producing log output.
-Here is an example call to an output method:
+Here is a call to an output method:
 
     logger.Info("hello", "key", value)
 
@@ -69,7 +69,6 @@ A logger's `With` method calls its handler's `WithAttrs` method.
 
 The `WithGroup` method is used to avoid avoid key collisions in large programs
 by establishing separate namespaces.
-
 This call creates a new `Logger` value with a group named "g":
 
     logger = logger.WithGroup("g")
@@ -140,8 +139,8 @@ func New(out io.Writer, opts *Options) *IndentHandler {
 
 We'll support only one option, the ability to set a minimum level in order to
 supress detailed log output.
-Handlers should always use the `slog.Leveler` type for this option.
-`Leveler` is implemented by both `Level` and `LevelVar`.
+Handlers should always declare this option to be a `slog.Leveler`.
+The `slog.Leveler` interface is implemented by both `Level` and `LevelVar`.
 A `Level` value is easy for the user to provide,
 but changing the level of multiple handlers requires tracking them all.
 If the user instead passes a `LevelVar`, then a single change to that `LevelVar`
@@ -220,7 +219,7 @@ groups established by `WithGroup`.
 
 5. Output the buffer.
 
-That is how our `IndentHandler`'s `Handle` method is structured:
+That is how `IndentHandler.Handle` is structured:
 
 ```
 func (h *IndentHandler) Handle(ctx context.Context, r slog.Record) error {
@@ -554,7 +553,7 @@ handler might look something like this:
         // ...
     }
 
-A single handleWidgets request might generate hundreds of log lines.
+A single `handleWidgets` request might generate hundreds of log lines.
 For instance, it might contain code like this:
 
     for _, w := range widgets {
@@ -728,7 +727,7 @@ func TestSlogtest(t *testing.T) {
 }
 ```
 
-Calling `TestHandler` is easy. The hard part is parsing the output.
+Calling `TestHandler` is easy. The hard part is parsing your handler's output.
 `TestHandler` calls your handler multiple times, resulting in a sequence of log
 entries.
 It is your job to parse each entry into a `map[string]any`.
@@ -851,8 +850,7 @@ like an invalid argument, is to panic or return an error.
 The built-in handlers do not follow that advice.
 Few things are more frustrating than being unable to debug a problem that
 causes logging to fail;
-our feeling is that it is
-better to produce some output, however imperfect, than to produce none at all.
+it is better to produce some output, however imperfect, than to produce none at all.
 That is why methods like `Logger.Info` convert programming bugs in their list of
 key-value pairs, like missing values or malformed keys,
 into `Attr`s that contain as much information as possible.
@@ -878,7 +876,7 @@ The most likely explanation for the issue is that a newer version of the `slog`
 a new `Kind`—a backwards-compatible change under the Go 1 Compatibility
 Promise—and the handler wasn't updated.
 That is certainly a problem, but it shouldn't deprive
-readers of the logs from seeing the rest of the output.
+readers from seeing the rest of the log output.
 
 There is one circumstance where returning an error from `Handler.Handle` is appropriate.
 If the output operation itself fails, the best course of action is to report
diff --git a/slog-handler-guide/guide.md b/slog-handler-guide/guide.md
index 92bcd261..969415f8 100644
--- a/slog-handler-guide/guide.md
+++ b/slog-handler-guide/guide.md
@@ -31,7 +31,7 @@ such as gathering key-value pairs into `Attr`s, and then call one or more
 and the output methods.
 
 An output method fulfills the main role of a logger: producing log output.
-Here is an example call to an output method:
+Here is a call to an output method:
 
     logger.Info("hello", "key", value)
 
@@ -56,7 +56,6 @@ A logger's `With` method calls its handler's `WithAttrs` method.
 
 The `WithGroup` method is used to avoid avoid key collisions in large programs
 by establishing separate namespaces.
-
 This call creates a new `Logger` value with a group named "g":
 
     logger = logger.WithGroup("g")
@@ -102,8 +101,8 @@ and the `New` function that constructs it from an `io.Writer` and options:
 
 We'll support only one option, the ability to set a minimum level in order to
 supress detailed log output.
-Handlers should always use the `slog.Leveler` type for this option.
-`Leveler` is implemented by both `Level` and `LevelVar`.
+Handlers should always declare this option to be a `slog.Leveler`.
+The `slog.Leveler` interface is implemented by both `Level` and `LevelVar`.
 A `Level` value is easy for the user to provide,
 but changing the level of multiple handlers requires tracking them all.
 If the user instead passes a `LevelVar`, then a single change to that `LevelVar`
@@ -178,7 +177,7 @@ groups established by `WithGroup`.
 
 5. Output the buffer.
 
-That is how our `IndentHandler`'s `Handle` method is structured:
+That is how `IndentHandler.Handle` is structured:
 
 %include indenthandler1/indent_handler.go handle -
 
@@ -373,7 +372,7 @@ handler might look something like this:
         // ...
     }
 
-A single handleWidgets request might generate hundreds of log lines.
+A single `handleWidgets` request might generate hundreds of log lines.
 For instance, it might contain code like this:
 
     for _, w := range widgets {
@@ -460,7 +459,7 @@ for our example handler:
 
 %include indenthandler3/indent_handler_test.go TestSlogtest -
 
-Calling `TestHandler` is easy. The hard part is parsing the output.
+Calling `TestHandler` is easy. The hard part is parsing your handler's output.
 `TestHandler` calls your handler multiple times, resulting in a sequence of log
 entries.
 It is your job to parse each entry into a `map[string]any`.
@@ -569,8 +568,7 @@ like an invalid argument, is to panic or return an error.
 The built-in handlers do not follow that advice.
 Few things are more frustrating than being unable to debug a problem that
 causes logging to fail;
-our feeling is that it is
-better to produce some output, however imperfect, than to produce none at all.
+it is better to produce some output, however imperfect, than to produce none at all.
 That is why methods like `Logger.Info` convert programming bugs in their list of
 key-value pairs, like missing values or malformed keys,
 into `Attr`s that contain as much information as possible.
@@ -596,7 +594,7 @@ The most likely explanation for the issue is that a newer version of the `slog`
 a new `Kind`—a backwards-compatible change under the Go 1 Compatibility
 Promise—and the handler wasn't updated.
 That is certainly a problem, but it shouldn't deprive
-readers of the logs from seeing the rest of the output.
+readers from seeing the rest of the log output.
 
 There is one circumstance where returning an error from `Handler.Handle` is appropriate.
 If the output operation itself fails, the best course of action is to report

From 9fd7daa707c37689879391242abc42be737c78e2 Mon Sep 17 00:00:00 2001
From: Jonathan Amsterdam 
Date: Thu, 3 Aug 2023 17:42:43 -0400
Subject: [PATCH 25/48] slog-handler-guide: prose tweak, re-make README

Change-Id: I470379d7ff14e8d3233556d70e499b19736f27a6
Reviewed-on: https://go-review.googlesource.com/c/example/+/515556
Run-TryBot: Jonathan Amsterdam 
TryBot-Result: Gopher Robot 
Reviewed-by: Ian Cottrell 
---
 slog-handler-guide/README.md | 4 ++--
 slog-handler-guide/guide.md  | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/slog-handler-guide/README.md b/slog-handler-guide/README.md
index 946f653d..89b6109a 100644
--- a/slog-handler-guide/README.md
+++ b/slog-handler-guide/README.md
@@ -910,7 +910,7 @@ so you can see all the log output to debug a crash.
 
 Allocation is often a major cause of a slow system.
 The `slog` package already works hard at minimizing allocations.
-If your handler does it own allocation, and profiling shows it to be
+If your handler does its own allocation, and profiling shows it to be
 a problem, then see if you can minimize it.
 
 One simple change you can make is to replace calls to `fmt.Sprintf` or `fmt.Appendf`
@@ -1010,4 +1010,4 @@ there indefinitely, constantly being reused, but with most of its capacity
 wasted.
 The extra memory might never be used again by the handler, and since it was in
 the handler's pool, it might never be garbage-collected for reuse elsewhere.
-We can avoid that situation by keeping large buffers out of the pool.
+We can avoid that situation by excluding large buffers from the pool.
diff --git a/slog-handler-guide/guide.md b/slog-handler-guide/guide.md
index 969415f8..7aa4d7e7 100644
--- a/slog-handler-guide/guide.md
+++ b/slog-handler-guide/guide.md
@@ -708,4 +708,4 @@ there indefinitely, constantly being reused, but with most of its capacity
 wasted.
 The extra memory might never be used again by the handler, and since it was in
 the handler's pool, it might never be garbage-collected for reuse elsewhere.
-We can avoid that situation by keeping large buffers out of the pool.
+We can avoid that situation by excluding large buffers from the pool.

From d9923f6970e9ba7e0d23aa9448ead71ea57235ae Mon Sep 17 00:00:00 2001
From: Jonathan Amsterdam 
Date: Fri, 1 Sep 2023 05:07:08 -0400
Subject: [PATCH 26/48] slog-handler-guide: fix link, mention ReplaceAttr

- Fix a broken link.

- When discussing handler options, mention ReplaceAttr.

This were both suggested by Peter Aronoff (peteraronoff@fastmail.com)
in a direct email.

Change-Id: I286142a1c5736691e32042e383f9a98e78f0fb49
Reviewed-on: https://go-review.googlesource.com/c/example/+/524758
Commit-Queue: Jonathan Amsterdam 
Reviewed-by: Alan Donovan 
TryBot-Bypass: Jonathan Amsterdam 
---
 slog-handler-guide/README.md | 10 +++++++++-
 slog-handler-guide/guide.md  | 10 +++++++++-
 2 files changed, 18 insertions(+), 2 deletions(-)

diff --git a/slog-handler-guide/README.md b/slog-handler-guide/README.md
index 89b6109a..712ec12e 100644
--- a/slog-handler-guide/README.md
+++ b/slog-handler-guide/README.md
@@ -147,6 +147,12 @@ If the user instead passes a `LevelVar`, then a single change to that `LevelVar`
 will change the behavior of all handlers that contain it.
 Changes to `LevelVar`s are goroutine-safe.
 
+You might also consider adding a `ReplaceAttr` option to your handler,
+like the [one for the built-in
+handlers](https://pkg.go.dev/log/slog#HandlerOptions.ReplaceAttr).
+Although `ReplaceAttr` will complicate your implementation, it will also
+make your handler more generally useful.
+
 The mutex will be used to ensure that writes to the `io.Writer` happen atomically.
 Unusually, `IndentHandler` holds a pointer to a `sync.Mutex` rather than holding a
 `sync.Mutex` directly.
@@ -504,7 +510,9 @@ number of calls to those methods, because of the repeated copying.
 That is unlikely to matter in practice, but if it bothers you,
 you can use a linked list instead,
 which `Handle` will have to reverse or visit recursively.
-See [github.com/jba/slog/withsupport](https://github.com/jba/slog/withsupport) for an implementation.
+See the
+[github.com/jba/slog/withsupport](https://github.com/jba/slog/tree/main/withsupport)
+package for an implementation.
 
 #### Getting the mutex right
 
diff --git a/slog-handler-guide/guide.md b/slog-handler-guide/guide.md
index 7aa4d7e7..fb4664d3 100644
--- a/slog-handler-guide/guide.md
+++ b/slog-handler-guide/guide.md
@@ -109,6 +109,12 @@ If the user instead passes a `LevelVar`, then a single change to that `LevelVar`
 will change the behavior of all handlers that contain it.
 Changes to `LevelVar`s are goroutine-safe.
 
+You might also consider adding a `ReplaceAttr` option to your handler,
+like the [one for the built-in
+handlers](https://pkg.go.dev/log/slog#HandlerOptions.ReplaceAttr).
+Although `ReplaceAttr` will complicate your implementation, it will also
+make your handler more generally useful.
+
 The mutex will be used to ensure that writes to the `io.Writer` happen atomically.
 Unusually, `IndentHandler` holds a pointer to a `sync.Mutex` rather than holding a
 `sync.Mutex` directly.
@@ -323,7 +329,9 @@ number of calls to those methods, because of the repeated copying.
 That is unlikely to matter in practice, but if it bothers you,
 you can use a linked list instead,
 which `Handle` will have to reverse or visit recursively.
-See [github.com/jba/slog/withsupport](https://github.com/jba/slog/withsupport) for an implementation.
+See the
+[github.com/jba/slog/withsupport](https://github.com/jba/slog/tree/main/withsupport)
+package for an implementation.
 
 #### Getting the mutex right
 

From 1d6d2400d4027025cb8edc86a139c9c581d672f7 Mon Sep 17 00:00:00 2001
From: Alan Donovan 
Date: Wed, 11 Oct 2023 17:52:55 -0400
Subject: [PATCH 27/48] gotypes: stop directing people to
 golang.org/x/tools/go/loader

It is very obsolete. Use go/packages instead.

Also, add a go generate command to weave the README.

Fixes golang/go#60593

Change-Id: Ifaf3ffa588dc3e65fb1e96b39b249a9084d66451
Reviewed-on: https://go-review.googlesource.com/c/example/+/534695
Reviewed-by: Robert Findley 
Run-TryBot: Robert Findley 
Auto-Submit: Alan Donovan 
TryBot-Result: Gopher Robot 
Commit-Queue: Alan Donovan 
---
 go.mod                    |   8 ++-
 go.sum                    |  32 +++--------
 gotypes/README.md         | 108 +++++++++++++++++++++-----------------
 gotypes/doc/main.go       |  60 ++++++++++++---------
 gotypes/gen.go            |   3 ++
 gotypes/go-types.md       |  50 ++++++++++--------
 gotypes/hugeparam/main.go |  28 +++++-----
 gotypes/skeleton/main.go  |  17 +++---
 8 files changed, 163 insertions(+), 143 deletions(-)
 create mode 100644 gotypes/gen.go

diff --git a/go.mod b/go.mod
index 1996732f..e876fb92 100644
--- a/go.mod
+++ b/go.mod
@@ -2,6 +2,10 @@ module golang.org/x/example
 
 go 1.18
 
-require golang.org/x/tools v0.0.0-20210112183307-1e6ecd4bf1b0
+require golang.org/x/tools v0.14.0
 
-require gopkg.in/yaml.v3 v3.0.1 // indirect
+require (
+	golang.org/x/mod v0.13.0 // indirect
+	golang.org/x/sys v0.13.0 // indirect
+	gopkg.in/yaml.v3 v3.0.1
+)
diff --git a/go.sum b/go.sum
index 114d0dcc..25e95bda 100644
--- a/go.sum
+++ b/go.sum
@@ -1,27 +1,11 @@
-github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
-golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20210112183307-1e6ecd4bf1b0 h1:iZhiQWrjyEuXG495d9MXkcmhrlxbQyGp0uNBY+YBZDk=
-golang.org/x/tools v0.0.0-20210112183307-1e6ecd4bf1b0/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
+golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
+golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
+golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/gotypes/README.md b/gotypes/README.md
index ee12bef9..9923e108 100644
--- a/gotypes/README.md
+++ b/gotypes/README.md
@@ -86,7 +86,7 @@ constant expressions, as we'll see in
 
 
 
-The [`golang.org/x/tools/go/loader` package](https://pkg.go.dev/golang.org/x/tools/go/loader)
+The [`golang.org/x/tools/go/packages` package](https://pkg.go.dev/golang.org/x/tools/go/packages)
 from the `x/tools` repository is a client of the type
 checker that loads, parses, and type-checks a complete Go program from
 source code.
@@ -2190,13 +2190,11 @@ programs.
 ```
 var bytesFlag = flag.Int("bytes", 48, "maximum parameter size in bytes")
 
-var sizeof = (&types.StdSizes{8, 8}).Sizeof // the sizeof function
-
-func PrintHugeParams(fset *token.FileSet, info *types.Info, files []*ast.File) {
+func PrintHugeParams(fset *token.FileSet, info *types.Info, sizes types.Sizes, files []*ast.File) {
 	checkTuple := func(descr string, tuple *types.Tuple) {
 		for i := 0; i < tuple.Len(); i++ {
 			v := tuple.At(i)
-			if sz := sizeof(v.Type()); sz > int64(*bytesFlag) {
+			if sz := sizes.Sizeof(v.Type()); sz > int64(*bytesFlag) {
 				fmt.Printf("%s: %q %s: %s = %d bytes\n",
 					fset.Position(v.Pos()),
 					v.Name(), descr, v.Type(), sz)
@@ -2296,25 +2294,28 @@ ran a `go install` or `go build -i` command.
 
 
 
-The [`golang.org/tools/x/go/loader` package](https://pkg.go.dev/golang.org/x/tools/go/loader)
-provides an alternative `Importer` that addresses
-some of these problems.
-It loads a complete program from source, performing
-[`cgo`](https://golang.org/cmd/cgo/cgo) preprocessing if
-necessary, followed by parsing and type-checking.
+The [`golang.org/tools/x/go/packages`
+package](https://pkg.go.dev/golang.org/x/tools/go/packages) provides
+a comprehensive means of loading packages from source.
+It runs `go list` to query the project metadata,
+performs [`cgo`](https://golang.org/cmd/cgo/cgo) preprocessing if necessary,
+reads and parses the source files,
+and optionally type-checks each package.
+It can load a whole program from source, or load just the initial
+packages from source and load all their dependencies from export data.
 It loads independent packages in parallel to hide I/O latency, and
 detects and reports import cycles.
 For each package, it provides the `types.Package` containing the
 package's lexical environment, the list of `ast.File` syntax
 trees for each file in the package, the `types.Info` containing
-type information for each syntax node, and a list of type errors
-associated with that package.
-(Please be aware that the `go/loader` package's API is likely to
-change before it finally stabilizes.)
-
-
-
-The `doc` program below demonstrates a simple use of the loader.
+type information for each syntax node, a list of type errors
+associated with that package, and other information too.
+Since some of this information is more costly to compute,
+the API allows you to select which parts you need,
+but since this is a tutorial we'll generally request complete
+information so that it is easier to explore.
+
+The `doc` program below demonstrates a simple use of `go/packages`.
 It is a rudimentary implementation of `go doc` that prints the type,
 methods, and documentation of the package-level object specified on
 the command line.
@@ -2324,10 +2325,10 @@ Here's an example:
 ```
 $ ./doc net/http File
 type net/http.File interface{Readdir(count int) ([]os.FileInfo, error); Seek(offset int64, whence int) (int64, error); Stat() (os.FileInfo, error); io.Closer; io.Reader}
-/go/src/io/io.go:92:2: method (net/http.File) Close() error
-/go/src/io/io.go:71:2: method (net/http.File) Read(p []byte) (n int, err error)
+$GOROOT/src/io/io.go:92:2: method (net/http.File) Close() error
+$GOROOT/src/io/io.go:71:2: method (net/http.File) Read(p []byte) (n int, err error)
 /go/src/net/http/fs.go:65:2: method (net/http.File) Readdir(count int) ([]os.FileInfo, error)
-/go/src/net/http/fs.go:66:2: method (net/http.File) Seek(offset int64, whence int) (int64, error)
+$GOROOT/src/net/http/fs.go:66:2: method (net/http.File) Seek(offset int64, whence int) (int64, error)
 /go/src/net/http/fs.go:67:2: method (net/http.File) Stat() (os.FileInfo, error)
 
  A File is returned by a FileSystem's Open method and can be
@@ -2340,8 +2341,10 @@ The methods should behave the same as those on an *os.File.
 Observe that it prints the correct location of each method
 declaration, even though, due to embedding, some of
 `http.File`'s methods were declared in another package.
-Here's the first part of the program, showing how to load an entire
-program starting from the single package, `pkgpath`:
+Here's the first part of the program, showing how to load
+complete type information including typed syntax,
+for a single package `pkgpath`,
+plus exported type information for its dependencies.
 
 
 	// go get golang.org/x/example/gotypes/doc
@@ -2349,24 +2352,28 @@ program starting from the single package, `pkgpath`:
 ```
 pkgpath, name := os.Args[1], os.Args[2]
 
-// The loader loads a complete Go program from source code.
-conf := loader.Config{ParserMode: parser.ParseComments}
-conf.Import(pkgpath)
-lprog, err := conf.Load()
+// Load complete type information for the specified packages,
+// along with type-annotated syntax.
+// Types for dependencies are loaded from export data.
+conf := &packages.Config{Mode: packages.LoadSyntax}
+pkgs, err := packages.Load(conf, pkgpath)
 if err != nil {
-	log.Fatal(err) // load error
+	log.Fatal(err) // failed to load anything
+}
+if packages.PrintErrors(pkgs) > 0 {
+	os.Exit(1) // some packages contained errors
 }
 
 // Find the package and package-level object.
-pkg := lprog.Package(pkgpath).Pkg
-obj := pkg.Scope().Lookup(name)
+pkg := pkgs[0]
+obj := pkg.Types.Scope().Lookup(name)
 if obj == nil {
-	log.Fatalf("%s.%s not found", pkg.Path(), name)
+	log.Fatalf("%s.%s not found", pkg.Types.Path(), name)
 }
 ```
 
 
-Notice that we instructed the parser to retain comments during parsing.
+By default, `go/packages`, instructs the parser to retain comments during parsing.
 The rest of the program prints the output:
 
 
@@ -2376,20 +2383,26 @@ The rest of the program prints the output:
 // Print the object and its methods (incl. location of definition).
 fmt.Println(obj)
 for _, sel := range typeutil.IntuitiveMethodSet(obj.Type(), nil) {
-	fmt.Printf("%s: %s\n", lprog.Fset.Position(sel.Obj().Pos()), sel)
+	fmt.Printf("%s: %s\n", pkg.Fset.Position(sel.Obj().Pos()), sel)
 }
 
 // Find the path from the root of the AST to the object's position.
 // Walk up to the enclosing ast.Decl for the doc comment.
-_, path, _ := lprog.PathEnclosingInterval(obj.Pos(), obj.Pos())
-for _, n := range path {
-	switch n := n.(type) {
-	case *ast.GenDecl:
-		fmt.Println("\n", n.Doc.Text())
-		return
-	case *ast.FuncDecl:
-		fmt.Println("\n", n.Doc.Text())
-		return
+for _, file := range pkg.Syntax {
+	pos := obj.Pos()
+	if !(file.FileStart <= pos && pos < file.FileEnd) {
+		continue // not in this file
+	}
+	path, _ := astutil.PathEnclosingInterval(file, pos, pos)
+	for _, n := range path {
+		switch n := n.(type) {
+		case *ast.GenDecl:
+			fmt.Println("\n", n.Doc.Text())
+			return
+		case *ast.FuncDecl:
+			fmt.Println("\n", n.Doc.Text())
+			return
+		}
 	}
 }
 ```
@@ -2535,11 +2548,10 @@ helper function
 [`astutil.PathEnclosingInterval`](https://pkg.go.dev/golang.org/x/tools/go/ast/astutil#PathEnclosingInterval).
 It returns the enclosing `ast.Node`, and all its ancestors up to
 the root of the file.
-You must know which file `*ast.File` the `token.Pos` belongs to.
-Alternatively, you can search an entire program loaded by the
-`loader` package, using
-[`(*loader.Program).PathEnclosingInterval`](https://pkg.go.dev/golang.org/x/tools/go/loader#Program.PathEnclosingInterval).
-
+If you don't know which file `*ast.File` the `token.Pos` belongs to,
+you can iterate over the parsed files of the package and quickly test
+whether its position falls within the file's range,
+from `File.FileStart` to `File.FileEnd`.
 
 
 To map **from an `Object` to its declaring syntax**, call
diff --git a/gotypes/doc/main.go b/gotypes/doc/main.go
index 3140f473..8e7027f0 100644
--- a/gotypes/doc/main.go
+++ b/gotypes/doc/main.go
@@ -4,12 +4,11 @@ package main
 import (
 	"fmt"
 	"go/ast"
-	"go/parser"
 	"log"
 	"os"
 
-	// TODO: these will use std go/types after Feb 2016
-	"golang.org/x/tools/go/loader"
+	"golang.org/x/tools/go/ast/astutil"
+	"golang.org/x/tools/go/packages"
 	"golang.org/x/tools/go/types/typeutil"
 )
 
@@ -20,19 +19,23 @@ func main() {
 	//!+part1
 	pkgpath, name := os.Args[1], os.Args[2]
 
-	// The loader loads a complete Go program from source code.
-	conf := loader.Config{ParserMode: parser.ParseComments}
-	conf.Import(pkgpath)
-	lprog, err := conf.Load()
+	// Load complete type information for the specified packages,
+	// along with type-annotated syntax.
+	// Types for dependencies are loaded from export data.
+	conf := &packages.Config{Mode: packages.LoadSyntax}
+	pkgs, err := packages.Load(conf, pkgpath)
 	if err != nil {
-		log.Fatal(err) // load error
+		log.Fatal(err) // failed to load anything
+	}
+	if packages.PrintErrors(pkgs) > 0 {
+		os.Exit(1) // some packages contained errors
 	}
 
 	// Find the package and package-level object.
-	pkg := lprog.Package(pkgpath).Pkg
-	obj := pkg.Scope().Lookup(name)
+	pkg := pkgs[0]
+	obj := pkg.Types.Scope().Lookup(name)
 	if obj == nil {
-		log.Fatalf("%s.%s not found", pkg.Path(), name)
+		log.Fatalf("%s.%s not found", pkg.Types.Path(), name)
 	}
 	//!-part1
 	//!+part2
@@ -40,33 +43,42 @@ func main() {
 	// Print the object and its methods (incl. location of definition).
 	fmt.Println(obj)
 	for _, sel := range typeutil.IntuitiveMethodSet(obj.Type(), nil) {
-		fmt.Printf("%s: %s\n", lprog.Fset.Position(sel.Obj().Pos()), sel)
+		fmt.Printf("%s: %s\n", pkg.Fset.Position(sel.Obj().Pos()), sel)
 	}
 
 	// Find the path from the root of the AST to the object's position.
 	// Walk up to the enclosing ast.Decl for the doc comment.
-	_, path, _ := lprog.PathEnclosingInterval(obj.Pos(), obj.Pos())
-	for _, n := range path {
-		switch n := n.(type) {
-		case *ast.GenDecl:
-			fmt.Println("\n", n.Doc.Text())
-			return
-		case *ast.FuncDecl:
-			fmt.Println("\n", n.Doc.Text())
-			return
+	for _, file := range pkg.Syntax {
+		pos := obj.Pos()
+		if !(file.FileStart <= pos && pos < file.FileEnd) {
+			continue // not in this file
+		}
+		path, _ := astutil.PathEnclosingInterval(file, pos, pos)
+		for _, n := range path {
+			switch n := n.(type) {
+			case *ast.GenDecl:
+				fmt.Println("\n", n.Doc.Text())
+				return
+			case *ast.FuncDecl:
+				fmt.Println("\n", n.Doc.Text())
+				return
+			}
 		}
 	}
 	//!-part2
 }
 
+// (The $GOROOT below is the actual string that appears in file names
+// loaded from export data for packages in the standard library.)
+
 /*
 //!+output
 $ ./doc net/http File
 type net/http.File interface{Readdir(count int) ([]os.FileInfo, error); Seek(offset int64, whence int) (int64, error); Stat() (os.FileInfo, error); io.Closer; io.Reader}
-/go/src/io/io.go:92:2: method (net/http.File) Close() error
-/go/src/io/io.go:71:2: method (net/http.File) Read(p []byte) (n int, err error)
+$GOROOT/src/io/io.go:92:2: method (net/http.File) Close() error
+$GOROOT/src/io/io.go:71:2: method (net/http.File) Read(p []byte) (n int, err error)
 /go/src/net/http/fs.go:65:2: method (net/http.File) Readdir(count int) ([]os.FileInfo, error)
-/go/src/net/http/fs.go:66:2: method (net/http.File) Seek(offset int64, whence int) (int64, error)
+$GOROOT/src/net/http/fs.go:66:2: method (net/http.File) Seek(offset int64, whence int) (int64, error)
 /go/src/net/http/fs.go:67:2: method (net/http.File) Stat() (os.FileInfo, error)
 
  A File is returned by a FileSystem's Open method and can be
diff --git a/gotypes/gen.go b/gotypes/gen.go
new file mode 100644
index 00000000..bce6efe7
--- /dev/null
+++ b/gotypes/gen.go
@@ -0,0 +1,3 @@
+//go:generate bash -c "go run ../internal/cmd/weave/weave.go ./go-types.md > README.md"
+
+package gotypes
diff --git a/gotypes/go-types.md b/gotypes/go-types.md
index 4cd3d812..ce5a430d 100644
--- a/gotypes/go-types.md
+++ b/gotypes/go-types.md
@@ -62,7 +62,7 @@ constant expressions, as we'll see in
 
 
 
-The [`golang.org/x/tools/go/loader` package](https://pkg.go.dev/golang.org/x/tools/go/loader)
+The [`golang.org/x/tools/go/packages` package](https://pkg.go.dev/golang.org/x/tools/go/packages)
 from the `x/tools` repository is a client of the type
 checker that loads, parses, and type-checks a complete Go program from
 source code.
@@ -1850,25 +1850,28 @@ ran a `go install` or `go build -i` command.
 
 
 
-The [`golang.org/tools/x/go/loader` package](https://pkg.go.dev/golang.org/x/tools/go/loader)
-provides an alternative `Importer` that addresses
-some of these problems.
-It loads a complete program from source, performing
-[`cgo`](https://golang.org/cmd/cgo/cgo) preprocessing if
-necessary, followed by parsing and type-checking.
+The [`golang.org/tools/x/go/packages`
+package](https://pkg.go.dev/golang.org/x/tools/go/packages) provides
+a comprehensive means of loading packages from source.
+It runs `go list` to query the project metadata,
+performs [`cgo`](https://golang.org/cmd/cgo/cgo) preprocessing if necessary,
+reads and parses the source files,
+and optionally type-checks each package.
+It can load a whole program from source, or load just the initial
+packages from source and load all their dependencies from export data.
 It loads independent packages in parallel to hide I/O latency, and
 detects and reports import cycles.
 For each package, it provides the `types.Package` containing the
 package's lexical environment, the list of `ast.File` syntax
 trees for each file in the package, the `types.Info` containing
-type information for each syntax node, and a list of type errors
-associated with that package.
-(Please be aware that the `go/loader` package's API is likely to
-change before it finally stabilizes.)
-
-
-
-The `doc` program below demonstrates a simple use of the loader.
+type information for each syntax node, a list of type errors
+associated with that package, and other information too.
+Since some of this information is more costly to compute,
+the API allows you to select which parts you need,
+but since this is a tutorial we'll generally request complete
+information so that it is easier to explore.
+
+The `doc` program below demonstrates a simple use of `go/packages`.
 It is a rudimentary implementation of `go doc` that prints the type,
 methods, and documentation of the package-level object specified on
 the command line.
@@ -1881,14 +1884,16 @@ Here's an example:
 Observe that it prints the correct location of each method
 declaration, even though, due to embedding, some of
 `http.File`'s methods were declared in another package.
-Here's the first part of the program, showing how to load an entire
-program starting from the single package, `pkgpath`:
+Here's the first part of the program, showing how to load
+complete type information including typed syntax,
+for a single package `pkgpath`,
+plus exported type information for its dependencies.
 
 
 %include doc/main.go part1
 
 
-Notice that we instructed the parser to retain comments during parsing.
+By default, `go/packages`, instructs the parser to retain comments during parsing.
 The rest of the program prints the output:
 
 
@@ -2035,11 +2040,10 @@ helper function
 [`astutil.PathEnclosingInterval`](https://pkg.go.dev/golang.org/x/tools/go/ast/astutil#PathEnclosingInterval).
 It returns the enclosing `ast.Node`, and all its ancestors up to
 the root of the file.
-You must know which file `*ast.File` the `token.Pos` belongs to.
-Alternatively, you can search an entire program loaded by the
-`loader` package, using
-[`(*loader.Program).PathEnclosingInterval`](https://pkg.go.dev/golang.org/x/tools/go/loader#Program.PathEnclosingInterval).
-
+If you don't know which file `*ast.File` the `token.Pos` belongs to,
+you can iterate over the parsed files of the package and quickly test
+whether its position falls within the file's range,
+from `File.FileStart` to `File.FileEnd`.
 
 
 To map **from an `Object` to its declaring syntax**, call
diff --git a/gotypes/hugeparam/main.go b/gotypes/hugeparam/main.go
index 80fc47e2..9577a090 100644
--- a/gotypes/hugeparam/main.go
+++ b/gotypes/hugeparam/main.go
@@ -12,20 +12,19 @@ import (
 	"go/token"
 	"go/types"
 	"log"
+	"os"
 
-	"golang.org/x/tools/go/loader"
+	"golang.org/x/tools/go/packages"
 )
 
 // !+
 var bytesFlag = flag.Int("bytes", 48, "maximum parameter size in bytes")
 
-var sizeof = (&types.StdSizes{8, 8}).Sizeof // the sizeof function
-
-func PrintHugeParams(fset *token.FileSet, info *types.Info, files []*ast.File) {
+func PrintHugeParams(fset *token.FileSet, info *types.Info, sizes types.Sizes, files []*ast.File) {
 	checkTuple := func(descr string, tuple *types.Tuple) {
 		for i := 0; i < tuple.Len(); i++ {
 			v := tuple.At(i)
-			if sz := sizeof(v.Type()); sz > int64(*bytesFlag) {
+			if sz := sizes.Sizeof(v.Type()); sz > int64(*bytesFlag) {
 				fmt.Printf("%s: %q %s: %s = %d bytes\n",
 					fset.Position(v.Pos()),
 					v.Name(), descr, v.Type(), sz)
@@ -54,19 +53,20 @@ func PrintHugeParams(fset *token.FileSet, info *types.Info, files []*ast.File) {
 func main() {
 	flag.Parse()
 
-	// The loader loads a complete Go program from source code.
-	var conf loader.Config
-	_, err := conf.FromArgs(flag.Args(), false)
+	// Load complete type information for the specified packages,
+	// along with type-annotated syntax and the "sizeof" function.
+	// Types for dependencies are loaded from export data.
+	conf := &packages.Config{Mode: packages.LoadSyntax}
+	pkgs, err := packages.Load(conf, flag.Args()...)
 	if err != nil {
-		log.Fatal(err) // command syntax error
+		log.Fatal(err) // failed to load anything
 	}
-	lprog, err := conf.Load()
-	if err != nil {
-		log.Fatal(err) // load error
+	if packages.PrintErrors(pkgs) > 0 {
+		os.Exit(1) // some packages contained errors
 	}
 
-	for _, info := range lprog.InitialPackages() {
-		PrintHugeParams(lprog.Fset, &info.Info, info.Files)
+	for _, pkg := range pkgs {
+		PrintHugeParams(pkg.Fset, pkg.TypesInfo, pkg.TypesSizes, pkg.Syntax)
 	}
 }
 
diff --git a/gotypes/skeleton/main.go b/gotypes/skeleton/main.go
index 1e6ee827..ab79b2c3 100644
--- a/gotypes/skeleton/main.go
+++ b/gotypes/skeleton/main.go
@@ -20,7 +20,7 @@ import (
 	"unicode"
 	"unicode/utf8"
 
-	"golang.org/x/tools/go/loader"
+	"golang.org/x/tools/go/packages"
 )
 
 const usage = "Usage: skeleton   "
@@ -76,15 +76,16 @@ func main() {
 	}
 	pkgpath, ifacename, concname := os.Args[1], os.Args[2], os.Args[3]
 
-	// The loader loads a complete Go program from source code.
-	var conf loader.Config
-	conf.Import(pkgpath)
-	lprog, err := conf.Load()
+	// Load only exported type information for the specified package.
+	conf := &packages.Config{Mode: packages.NeedTypes}
+	pkgs, err := packages.Load(conf, pkgpath)
 	if err != nil {
-		log.Fatal(err) // load error
+		log.Fatal(err) // failed to load anything
 	}
-	pkg := lprog.Package(pkgpath).Pkg
-	if err := PrintSkeleton(pkg, ifacename, concname); err != nil {
+	if packages.PrintErrors(pkgs) > 0 {
+		os.Exit(1) // some packages contained errors
+	}
+	if err := PrintSkeleton(pkgs[0].Types, ifacename, concname); err != nil {
 		log.Fatal(err)
 	}
 }

From 32022caedd6a177a7717aa8680cbe179e1045935 Mon Sep 17 00:00:00 2001
From: Aleksandr Shalimov 
Date: Mon, 5 Feb 2024 16:20:47 +0000
Subject: [PATCH 28/48] slog-handler-guide/README.md: fix word duplication

Change-Id: Id17b4acd73e237813292a63112b30e915d652bd4
GitHub-Last-Rev: 2052832a0d60d4b417f94e25fcb6802efd2aaf39
GitHub-Pull-Request: golang/example#29
Reviewed-on: https://go-review.googlesource.com/c/example/+/550196
Reviewed-by: qiulaidongfeng <2645477756@qq.com>
Run-TryBot: qiulaidongfeng <2645477756@qq.com>
TryBot-Result: Gopher Robot 
Reviewed-by: Robert Griesemer 
Reviewed-by: Jonathan Amsterdam 
TryBot-Bypass: Jonathan Amsterdam 
---
 slog-handler-guide/README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/slog-handler-guide/README.md b/slog-handler-guide/README.md
index 712ec12e..00eb1e5b 100644
--- a/slog-handler-guide/README.md
+++ b/slog-handler-guide/README.md
@@ -67,7 +67,7 @@ original remains unchanged.
 All subsequent output from `logger` will include those attributes.
 A logger's `With` method calls its handler's `WithAttrs` method.
 
-The `WithGroup` method is used to avoid avoid key collisions in large programs
+The `WithGroup` method is used to avoid key collisions in large programs
 by establishing separate namespaces.
 This call creates a new `Logger` value with a group named "g":
 

From 39e772fc26705bb170db248e5372a81ed5ffd67f Mon Sep 17 00:00:00 2001
From: Russ Cox 
Date: Tue, 16 Jul 2024 11:35:19 -0400
Subject: [PATCH 29/48] LICENSE: update per Google Legal

Very minor tweaks:
 - Remove (c) pseudosymbol.
 - Remove "All Rights Reserved."
 - Change "Google Inc." (no longer exists) to "Google LLC".

[git-generate]
echo '
,s/\(c\) //
,s/ All rights reserved.//
,s/Google Inc./Google LLC/
w
q
' | sam -d LICENSE

Change-Id: I450cba7aff2cb2360dafb238cefd9864c232ac1d
Reviewed-on: https://go-review.googlesource.com/c/example/+/598577
Commit-Queue: Russ Cox 
Reviewed-by: Ian Lance Taylor 
Auto-Submit: Russ Cox 
TryBot-Bypass: Russ Cox 
---
 LICENSE | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/LICENSE b/LICENSE
index 6a66aea5..2a7cf70d 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2009 The Go Authors. All rights reserved.
+Copyright 2009 The Go Authors.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are
@@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer.
 copyright notice, this list of conditions and the following disclaimer
 in the documentation and/or other materials provided with the
 distribution.
-   * Neither the name of Google Inc. nor the names of its
+   * Neither the name of Google LLC nor the names of its
 contributors may be used to endorse or promote products derived from
 this software without specific prior written permission.
 

From f942a68fe2fa374511db3a6854df88d98494f0f2 Mon Sep 17 00:00:00 2001
From: Eli Bendersky 
Date: Fri, 23 Aug 2024 06:32:29 -0700
Subject: [PATCH 30/48] ragserver: start adding new example of a RAG server in
 Go

Change-Id: I256449c9cd97ef53251e326d943858c237b4ea16
Reviewed-on: https://go-review.googlesource.com/c/example/+/608055
Reviewed-by: Ian Lance Taylor 
Auto-Submit: Eli Bendersky 
TryBot-Bypass: Eli Bendersky 
Reviewed-by: Eli Bendersky 
---
 ragserver/README.md                        |  54 ++++
 ragserver/ragserver/go.mod                 |  63 ++++
 ragserver/ragserver/go.sum                 | 325 +++++++++++++++++++++
 ragserver/ragserver/json.go                |  44 +++
 ragserver/ragserver/main.go                | 246 ++++++++++++++++
 ragserver/ragserver/weaviate.go            |  63 ++++
 ragserver/tests/add-documents.sh           |  19 ++
 ragserver/tests/docker-compose.yml         |  22 ++
 ragserver/tests/query.sh                   |  24 ++
 ragserver/tests/weaviate-delete-objects.sh |   7 +
 ragserver/tests/weaviate-show-all.sh       |  17 ++
 11 files changed, 884 insertions(+)
 create mode 100644 ragserver/README.md
 create mode 100644 ragserver/ragserver/go.mod
 create mode 100644 ragserver/ragserver/go.sum
 create mode 100644 ragserver/ragserver/json.go
 create mode 100644 ragserver/ragserver/main.go
 create mode 100644 ragserver/ragserver/weaviate.go
 create mode 100755 ragserver/tests/add-documents.sh
 create mode 100644 ragserver/tests/docker-compose.yml
 create mode 100755 ragserver/tests/query.sh
 create mode 100755 ragserver/tests/weaviate-delete-objects.sh
 create mode 100755 ragserver/tests/weaviate-show-all.sh

diff --git a/ragserver/README.md b/ragserver/README.md
new file mode 100644
index 00000000..2ccfdc0c
--- /dev/null
+++ b/ragserver/README.md
@@ -0,0 +1,54 @@
+# ragserver
+
+*RAG stands for Retrieval Augmented Generation*
+
+Demos of implementing a "RAG Server" in Go, using [Google
+AI](https://ai.google.dev/) for embeddings and language models and
+[Weaviate](https://weaviate.io/) as a vector database.
+
+
+## How it works
+
+The server we're developing is a standard Go HTTP server, listening on a local
+port. See the next section for the request schema for this server. It supports
+adding new documents to its context, and getting queries that would use this
+context.
+
+Weaviate has the be installed locally; the easiest way to do so is by using
+`docker-compose` as described in the Usage section.
+
+## Server request schema
+
+```
+/add/: POST {"documents": [{"text": "..."}, {"text": "..."}, ...]}
+  response: OK (no body)
+
+/query/: GET {"content": "..."}
+  response: model response as a string
+```
+
+## Server variants
+
+* `ragserver`: uses the Google AI Go SDK directly for LLM calls and embeddings,
+  and the Weaviate Go client library directly for interacting with Weaviate.
+
+## Usage
+
+* In terminal window 1, `cd tests` and run `docker-compose up`;
+  This will start the weaviate service in the foreground.
+* In terminal window 2, run `GEMINI_API_KEY=... go run .` in the tested
+  `ragserver` directory.
+* In terminal window 3, we can now run scripts to clear/populate the
+  weaviate DB and interact with `ragserver`. The following instructions are
+  for terminal window 3.
+
+Run `cd tests`; then we can clear out the weaviate DB with
+`./weaviate-delete-objects.sh`. To add documents to the DB through `ragserver`,
+run `./add-documents.sh`. For a sample query, run `./query.sh`
+Adjust the contents of these scripts as needed.
+
+## Environment variables
+
+* `SERVERPORT`: the port this server is listening on (default 9020)
+* `WVPORT`: the port Weaviate is listening on (default 9035)
+* `GEMINI_API_KEY`: API key for the Gemini service at https://ai.google.dev
diff --git a/ragserver/ragserver/go.mod b/ragserver/ragserver/go.mod
new file mode 100644
index 00000000..c5ccbee7
--- /dev/null
+++ b/ragserver/ragserver/go.mod
@@ -0,0 +1,63 @@
+module golang.org/x/example/ragserver/ragserver
+
+go 1.23.0
+
+require (
+	github.com/google/generative-ai-go v0.17.0
+	github.com/weaviate/weaviate v1.26.1
+	github.com/weaviate/weaviate-go-client/v4 v4.15.1
+	google.golang.org/api v0.194.0
+)
+
+require (
+	cloud.google.com/go v0.115.1 // indirect
+	cloud.google.com/go/ai v0.8.0 // indirect
+	cloud.google.com/go/auth v0.9.1 // indirect
+	cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
+	cloud.google.com/go/compute/metadata v0.5.0 // indirect
+	cloud.google.com/go/longrunning v0.5.7 // indirect
+	github.com/PuerkitoBio/purell v1.1.1 // indirect
+	github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
+	github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
+	github.com/felixge/httpsnoop v1.0.4 // indirect
+	github.com/go-logr/logr v1.4.2 // indirect
+	github.com/go-logr/stdr v1.2.2 // indirect
+	github.com/go-openapi/analysis v0.21.2 // indirect
+	github.com/go-openapi/errors v0.22.0 // indirect
+	github.com/go-openapi/jsonpointer v0.19.5 // indirect
+	github.com/go-openapi/jsonreference v0.19.6 // indirect
+	github.com/go-openapi/loads v0.21.1 // indirect
+	github.com/go-openapi/spec v0.20.4 // indirect
+	github.com/go-openapi/strfmt v0.23.0 // indirect
+	github.com/go-openapi/swag v0.22.3 // indirect
+	github.com/go-openapi/validate v0.21.0 // indirect
+	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
+	github.com/google/s2a-go v0.1.8 // indirect
+	github.com/google/uuid v1.6.0 // indirect
+	github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
+	github.com/googleapis/gax-go/v2 v2.13.0 // indirect
+	github.com/josharian/intern v1.0.0 // indirect
+	github.com/mailru/easyjson v0.7.7 // indirect
+	github.com/mitchellh/mapstructure v1.5.0 // indirect
+	github.com/oklog/ulid v1.3.1 // indirect
+	github.com/pkg/errors v0.9.1 // indirect
+	go.mongodb.org/mongo-driver v1.14.0 // indirect
+	go.opencensus.io v0.24.0 // indirect
+	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 // indirect
+	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect
+	go.opentelemetry.io/otel v1.26.0 // indirect
+	go.opentelemetry.io/otel/metric v1.26.0 // indirect
+	go.opentelemetry.io/otel/trace v1.26.0 // indirect
+	golang.org/x/crypto v0.26.0 // indirect
+	golang.org/x/net v0.28.0 // indirect
+	golang.org/x/oauth2 v0.22.0 // indirect
+	golang.org/x/sync v0.8.0 // indirect
+	golang.org/x/sys v0.24.0 // indirect
+	golang.org/x/text v0.17.0 // indirect
+	golang.org/x/time v0.6.0 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
+	google.golang.org/grpc v1.65.0 // indirect
+	google.golang.org/protobuf v1.34.2 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/ragserver/ragserver/go.sum b/ragserver/ragserver/go.sum
new file mode 100644
index 00000000..06d61545
--- /dev/null
+++ b/ragserver/ragserver/go.sum
@@ -0,0 +1,325 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.115.1 h1:Jo0SM9cQnSkYfp44+v+NQXHpcHqlnRJk2qxh6yvxxxQ=
+cloud.google.com/go v0.115.1/go.mod h1:DuujITeaufu3gL68/lOFIirVNJwQeyf5UXyi+Wbgknc=
+cloud.google.com/go/ai v0.8.0 h1:rXUEz8Wp2OlrM8r1bfmpF2+VKqc1VJpafE3HgzRnD/w=
+cloud.google.com/go/ai v0.8.0/go.mod h1:t3Dfk4cM61sytiggo2UyGsDVW3RF1qGZaUKDrZFyqkE=
+cloud.google.com/go/auth v0.9.1 h1:+pMtLEV2k0AXKvs/tGZojuj6QaioxfUjOpMsG5Gtx+w=
+cloud.google.com/go/auth v0.9.1/go.mod h1:Sw8ocT5mhhXxFklyhT12Eiy0ed6tTrPMCJjSI8KhYLk=
+cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY=
+cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc=
+cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
+cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
+cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
+cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
+github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
+github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
+github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
+github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-openapi/analysis v0.21.2 h1:hXFrOYFHUAMQdu6zwAiKKJHJQ8kqZs1ux/ru1P1wLJU=
+github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY=
+github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
+github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
+github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w=
+github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE=
+github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
+github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
+github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
+github.com/go-openapi/loads v0.21.1 h1:Wb3nVZpdEzDTcly8S4HMkey6fjARRzb7iEaySimlDW0=
+github.com/go-openapi/loads v0.21.1/go.mod h1:/DtAMXXneXFjbQMGEtbamCZb+4x7eGwkvZCvBmwUG+g=
+github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
+github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
+github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg=
+github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k=
+github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c=
+github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4=
+github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
+github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
+github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
+github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
+github.com/go-openapi/validate v0.21.0 h1:+Wqk39yKOhfpLqNLEC0/eViCkzM5FVXVqrvt526+wcI=
+github.com/go-openapi/validate v0.21.0/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
+github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
+github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
+github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
+github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
+github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs=
+github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
+github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
+github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk=
+github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28=
+github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo=
+github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk=
+github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw=
+github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360=
+github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg=
+github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE=
+github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8=
+github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
+github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
+github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
+github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
+github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
+github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
+github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/generative-ai-go v0.17.0 h1:kUmCXUIwJouD7I7ev3OmxzzQVICyhIWAxaXk2yblCMY=
+github.com/google/generative-ai-go v0.17.0/go.mod h1:JYolL13VG7j79kM5BtHz4qwONHkeJQzOCkKXnpqtS/E=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
+github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
+github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
+github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
+github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s=
+github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
+github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
+github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
+github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
+github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
+github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
+github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
+github.com/weaviate/weaviate v1.26.1 h1:Vecl/mX5dt6z6E27k5bjAnkNS1hvWNiVB0+yMY29BsU=
+github.com/weaviate/weaviate v1.26.1/go.mod h1:o6nFEB4UozA+B2fnAWyF0HZqB2ab44pRhJHYwvxVyyA=
+github.com/weaviate/weaviate-go-client/v4 v4.15.1 h1:zw+aw8DsZZkSiFyZOkuKKK49y0jnNr+4nKXCUKsyR/Q=
+github.com/weaviate/weaviate-go-client/v4 v4.15.1/go.mod h1:0aUsHXNfsdRfbO46t4Us5KUHqkhY+hlHevK6fBu72J4=
+github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
+github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
+github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
+github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
+go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg=
+go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng=
+go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
+go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
+go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
+go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 h1:A3SayB3rNyt+1S6qpI9mHPkeHTZbD7XILEqWnYZb2l0=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0/go.mod h1:27iA5uvhuRNmalO+iEUdVn5ZMj2qy10Mm+XRIpRmyuU=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc=
+go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs=
+go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4=
+go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30=
+go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4=
+go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA=
+go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
+golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
+golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
+golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
+golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
+golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
+golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
+golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
+golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.194.0 h1:dztZKG9HgtIpbI35FhfuSNR/zmaMVdxNlntHj1sIS4s=
+google.golang.org/api v0.194.0/go.mod h1:AgvUFdojGANh3vI+P7EVnxj3AISHllxGCJSFmggmnd0=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f h1:b1Ln/PG8orm0SsBbHZWke8dDp2lrCD4jSmfglFpTZbk=
+google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f/go.mod h1:AHT0dDg3SoMOgZGnZk29b5xTbPHMoEC8qthmBLJCpys=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
+google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
+google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
+google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/ragserver/ragserver/json.go b/ragserver/ragserver/json.go
new file mode 100644
index 00000000..c0bb7ce6
--- /dev/null
+++ b/ragserver/ragserver/json.go
@@ -0,0 +1,44 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"encoding/json"
+	"fmt"
+	"mime"
+	"net/http"
+)
+
+// readRequestJSON expects req to have a JSON content type with a body that
+// contains a JSON-encoded value complying with the underlying type of target.
+// It populates target, or returns an error.
+func readRequestJSON(target any, req *http.Request) error {
+	contentType := req.Header.Get("Content-Type")
+	mediaType, _, err := mime.ParseMediaType(contentType)
+	if err != nil {
+		return err
+	}
+	if mediaType != "application/json" {
+		return fmt.Errorf("expect application/json Content-Type, got %s", mediaType)
+	}
+
+	dec := json.NewDecoder(req.Body)
+	dec.DisallowUnknownFields()
+	if err := dec.Decode(target); err != nil {
+		return err
+	}
+	return nil
+}
+
+// renderJSON renders 'v' as JSON and writes it as a response into w.
+func renderJSON(w http.ResponseWriter, v interface{}) {
+	js, err := json.Marshal(v)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+	w.Header().Set("Content-Type", "application/json")
+	w.Write(js)
+}
diff --git a/ragserver/ragserver/main.go b/ragserver/ragserver/main.go
new file mode 100644
index 00000000..e55c66fe
--- /dev/null
+++ b/ragserver/ragserver/main.go
@@ -0,0 +1,246 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Command ragserver is an HTTP server that implements RAG (Retrieval
+// Augmented Generation) using the Gemini model and Weaviate. See the
+// accompanying README file for additional details.
+package main
+
+import (
+	"cmp"
+	"context"
+	"fmt"
+	"log"
+	"net/http"
+	"os"
+	"strings"
+
+	"github.com/google/generative-ai-go/genai"
+	"github.com/weaviate/weaviate-go-client/v4/weaviate"
+	"github.com/weaviate/weaviate-go-client/v4/weaviate/graphql"
+	"github.com/weaviate/weaviate/entities/models"
+	"google.golang.org/api/option"
+)
+
+const generativeModelName = "gemini-1.5-flash"
+const embeddingModelName = "text-embedding-004"
+
+// This is a standard Go HTTP server. Server state is in the ragServer struct.
+// The `main` function connects to the required services (Weaviate and Google
+// AI), initializes the server state and registers HTTP handlers.
+func main() {
+	ctx := context.Background()
+	wvClient, err := initWeaviate(ctx)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	apiKey := os.Getenv("GEMINI_API_KEY")
+	genaiClient, err := genai.NewClient(ctx, option.WithAPIKey(apiKey))
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer genaiClient.Close()
+
+	server := &ragServer{
+		ctx:      ctx,
+		wvClient: wvClient,
+		genModel: genaiClient.GenerativeModel(generativeModelName),
+		embModel: genaiClient.EmbeddingModel(embeddingModelName),
+	}
+
+	mux := http.NewServeMux()
+	mux.HandleFunc("POST /add/", server.addDocumentsHandler)
+	mux.HandleFunc("GET /query/", server.queryHandler)
+
+	port := cmp.Or(os.Getenv("SERVERPORT"), "9020")
+	address := "localhost:" + port
+	log.Println("listening on", address)
+	log.Fatal(http.ListenAndServe(address, mux))
+}
+
+type ragServer struct {
+	ctx      context.Context
+	wvClient *weaviate.Client
+	genModel *genai.GenerativeModel
+	embModel *genai.EmbeddingModel
+}
+
+func (rs *ragServer) addDocumentsHandler(w http.ResponseWriter, req *http.Request) {
+	// Parse HTTP request from JSON.
+	type document struct {
+		Text string
+	}
+	type addRequest struct {
+		Documents []document
+	}
+	ar := &addRequest{}
+
+	err := readRequestJSON(ar, req)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+
+	// Use the batch embedding API to embed all documents at once.
+	batch := rs.embModel.NewBatch()
+	for _, doc := range ar.Documents {
+		batch.AddContent(genai.Text(doc.Text))
+	}
+	log.Printf("invoking embedding model with %v documents", len(ar.Documents))
+	rsp, err := rs.embModel.BatchEmbedContents(rs.ctx, batch)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+	if len(rsp.Embeddings) != len(ar.Documents) {
+		http.Error(w, "embedded batch size mismatch", http.StatusInternalServerError)
+		return
+	}
+
+	// Convert our documents - along with their embedding vectors - into types
+	// used by the Weaviate client library.
+	objects := make([]*models.Object, len(ar.Documents))
+	for i, doc := range ar.Documents {
+		objects[i] = &models.Object{
+			Class: "Document",
+			Properties: map[string]any{
+				"text": doc.Text,
+			},
+			Vector: rsp.Embeddings[i].Values,
+		}
+	}
+
+	// Store documents with embeddings in the Weaviate DB.
+	log.Printf("storing %v objects in weaviate", len(objects))
+	_, err = rs.wvClient.Batch().ObjectsBatcher().WithObjects(objects...).Do(rs.ctx)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+}
+
+func (rs *ragServer) queryHandler(w http.ResponseWriter, req *http.Request) {
+	// Parse HTTP request from JSON.
+	type queryRequest struct {
+		Content string
+	}
+	qr := &queryRequest{}
+	err := readRequestJSON(qr, req)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+
+	// Embed the query contents.
+	rsp, err := rs.embModel.EmbedContent(rs.ctx, genai.Text(qr.Content))
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+
+	// Search weaviate to find the most relevant (closest in vector space)
+	// documents to the query.
+	gql := rs.wvClient.GraphQL()
+	result, err := gql.Get().
+		WithNearVector(
+			gql.NearVectorArgBuilder().WithVector(rsp.Embedding.Values)).
+		WithClassName("Document").
+		WithFields(graphql.Field{Name: "text"}).
+		WithLimit(3).
+		Do(rs.ctx)
+	if werr := combinedWeaviateError(result, err); werr != nil {
+		http.Error(w, werr.Error(), http.StatusInternalServerError)
+		return
+	}
+
+	contents, err := decodeGetResults(result)
+	if err != nil {
+		http.Error(w, fmt.Errorf("reading weaviate response: %w", err).Error(), http.StatusInternalServerError)
+		return
+	}
+
+	// Creata a RAG query for the LLM with the most relevant documents as
+	// context.
+	ragQuery := fmt.Sprintf(ragTemplateStr, qr.Content, strings.Join(contents, "\n"))
+	resp, err := rs.genModel.GenerateContent(rs.ctx, genai.Text(ragQuery))
+	if err != nil {
+		log.Printf("calling generative model: %v", err.Error())
+		http.Error(w, "generative model error", http.StatusInternalServerError)
+		return
+	}
+
+	if len(resp.Candidates) != 1 {
+		log.Printf("got %v candidates, expected 1", len(resp.Candidates))
+		http.Error(w, "generative model error", http.StatusInternalServerError)
+		return
+	}
+
+	var respTexts []string
+	for _, part := range resp.Candidates[0].Content.Parts {
+		if pt, ok := part.(genai.Text); ok {
+			respTexts = append(respTexts, string(pt))
+		} else {
+			log.Printf("bad type of part: %v", pt)
+			http.Error(w, "generative model error", http.StatusInternalServerError)
+			return
+		}
+	}
+
+	renderJSON(w, strings.Join(respTexts, "\n"))
+}
+
+const ragTemplateStr = `
+I will ask you a question and will provide some additional context information.
+Assume this context information is factual and correct, as part of internal
+documentation.
+If the question relates to the context, answer it using the context.
+If the question does not relate to the context, answer it as normal.
+
+For example, let's say the context has nothing in it about tropical flowers;
+then if I ask you about tropical flowers, just answer what you know about them
+without referring to the context.
+
+For example, if the context does mention minerology and I ask you about that,
+provide information from the context along with general knowledge.
+
+Question:
+%s
+
+Context:
+%s
+`
+
+// decodeGetResults decodes the result returned by Weaviate's GraphQL Get
+// query; these are returned as a nested map[string]any (just like JSON
+// unmarshaled into a map[string]any). We have to extract all document contents
+// as a list of strings.
+func decodeGetResults(result *models.GraphQLResponse) ([]string, error) {
+	data, ok := result.Data["Get"]
+	if !ok {
+		return nil, fmt.Errorf("Get key not found in result")
+	}
+	doc, ok := data.(map[string]any)
+	if !ok {
+		return nil, fmt.Errorf("Get key unexpected type")
+	}
+	slc, ok := doc["Document"].([]any)
+	if !ok {
+		return nil, fmt.Errorf("Document is not a list of results")
+	}
+
+	var out []string
+	for _, s := range slc {
+		smap, ok := s.(map[string]any)
+		if !ok {
+			return nil, fmt.Errorf("invalid element in list of documents")
+		}
+		s, ok := smap["text"].(string)
+		if !ok {
+			return nil, fmt.Errorf("expected string in list of documents")
+		}
+		out = append(out, s)
+	}
+	return out, nil
+}
diff --git a/ragserver/ragserver/weaviate.go b/ragserver/ragserver/weaviate.go
new file mode 100644
index 00000000..3b6b9b20
--- /dev/null
+++ b/ragserver/ragserver/weaviate.go
@@ -0,0 +1,63 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+// Utilities for working with Weaviate.
+
+import (
+	"cmp"
+	"context"
+	"fmt"
+	"os"
+
+	"github.com/weaviate/weaviate-go-client/v4/weaviate"
+	"github.com/weaviate/weaviate/entities/models"
+)
+
+// initWeaviate initializes a weaviate client for our application.
+func initWeaviate(ctx context.Context) (*weaviate.Client, error) {
+	client, err := weaviate.NewClient(weaviate.Config{
+		Host:   "localhost:" + cmp.Or(os.Getenv("WVPORT"), "9035"),
+		Scheme: "http",
+	})
+	if err != nil {
+		return nil, fmt.Errorf("initializing weaviate: %w", err)
+	}
+
+	// Create a new class (collection) in weaviate if it doesn't exist yet.
+	cls := &models.Class{
+		Class:      "Document",
+		Vectorizer: "none",
+	}
+	exists, err := client.Schema().ClassExistenceChecker().WithClassName(cls.Class).Do(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("weaviate error: %w", err)
+	}
+	if !exists {
+		err = client.Schema().ClassCreator().WithClass(cls).Do(ctx)
+		if err != nil {
+			return nil, fmt.Errorf("weaviate error: %w", err)
+		}
+	}
+
+	return client, nil
+}
+
+// combinedWeaviateError generates an error if err is non-nil or result has
+// errors, and returns an error (or nil if there's no error). It's useful for
+// the results of the Weaviate GraphQL API's "Do" calls.
+func combinedWeaviateError(result *models.GraphQLResponse, err error) error {
+	if err != nil {
+		return err
+	}
+	if len(result.Errors) != 0 {
+		var ss []string
+		for _, e := range result.Errors {
+			ss = append(ss, e.Message)
+		}
+		return fmt.Errorf("weaviate error: %v", ss)
+	}
+	return nil
+}
diff --git a/ragserver/tests/add-documents.sh b/ragserver/tests/add-documents.sh
new file mode 100755
index 00000000..8ec8f3e3
--- /dev/null
+++ b/ragserver/tests/add-documents.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+# This script interacts with a running ragserver to add some example documents.
+
+set -eux
+
+echo '{
+	"documents": [
+	{"text": "TDXIRV is an environment variable for controlling throttle speed"},
+	{"text": "some flags for setting acceleration are --accelxyzp and --acceljjrv"},
+	{"text": "acceleration is also affected by the ACCUVI5 env var"},
+	{"text": "/usr/local/fuel555 contains information about fuel capacity"},
+	{"text": "we can control fuel savings with the --savemyfuelplease flag"},
+	{"text": "fuel savings can be observed on local port 48332"}
+]}' | tr -d "\n" | curl \
+		-X POST \
+    -H 'Content-Type: application/json' \
+    -d @- \
+    http://localhost:9020/add/
diff --git a/ragserver/tests/docker-compose.yml b/ragserver/tests/docker-compose.yml
new file mode 100644
index 00000000..81f45cd1
--- /dev/null
+++ b/ragserver/tests/docker-compose.yml
@@ -0,0 +1,22 @@
+---
+version: '3.4'
+services:
+  weaviate:
+    command:
+    - --host
+    - 0.0.0.0
+    - --port
+    - '9035'
+    - --scheme
+    - http
+    image: cr.weaviate.io/semitechnologies/weaviate:1.25.2
+    ports:
+    - 9035:9035
+    - 50051:50051
+    restart: on-failure:0
+    environment:
+      QUERY_DEFAULTS_LIMIT: 25
+      AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: 'true'
+      PERSISTENCE_DATA_PATH: '/var/lib/weaviate'
+      CLUSTER_HOSTNAME: 'node1'
+...
diff --git a/ragserver/tests/query.sh b/ragserver/tests/query.sh
new file mode 100755
index 00000000..9013eb9c
--- /dev/null
+++ b/ragserver/tests/query.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+set -eu
+
+# Check if an argument is provided
+if [ $# -eq 0 ]; then
+    echo "Usage: $0 ''"
+    exit 1
+fi
+
+set -x
+
+# Capture the query from the command-line argument
+QUERY_CONTENT=$1
+
+# Build the JSON payload
+PAYLOAD=$(echo "{\"content\": \"$QUERY_CONTENT\"}")
+
+# Send the request
+echo "$PAYLOAD" | tr -d "\n" | curl \
+    -X GET \
+    -H 'Content-Type: application/json' \
+    -d @- \
+    http://localhost:9020/query/ | sed 's/\\n/\n/g'
diff --git a/ragserver/tests/weaviate-delete-objects.sh b/ragserver/tests/weaviate-delete-objects.sh
new file mode 100755
index 00000000..766dfc94
--- /dev/null
+++ b/ragserver/tests/weaviate-delete-objects.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+set -eux
+
+curl \
+  -X DELETE \
+  http://localhost:9035/v1/schema/Document
diff --git a/ragserver/tests/weaviate-show-all.sh b/ragserver/tests/weaviate-show-all.sh
new file mode 100755
index 00000000..b49d01bc
--- /dev/null
+++ b/ragserver/tests/weaviate-show-all.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+set -eux
+
+echo '{
+  "query": "{
+    Get {
+      Document { 
+        text
+      }
+    }
+  }"
+}' | tr -d "\n" | curl \
+    -X POST \
+    -H 'Content-Type: application/json' \
+    -d @- \
+    http://localhost:9035/v1/graphql | jq .

From 131af90c4676e7cd53402cf027cef01579dbaf6b Mon Sep 17 00:00:00 2001
From: Eli Bendersky 
Date: Tue, 27 Aug 2024 05:41:54 -0700
Subject: [PATCH 31/48] ragserver: add ragserver-langchaingo variant

Change-Id: I76c6fc25ee8cf6a8c97ed23502b116aa3d3a6041
Reviewed-on: https://go-review.googlesource.com/c/example/+/608735
Reviewed-by: Ian Lance Taylor 
TryBot-Bypass: Eli Bendersky 
Reviewed-by: Eli Bendersky 
Auto-Submit: Eli Bendersky 
---
 ragserver/README.md                     |   2 +
 ragserver/ragserver-langchaingo/go.mod  |  70 ++++
 ragserver/ragserver-langchaingo/go.sum  | 465 ++++++++++++++++++++++++
 ragserver/ragserver-langchaingo/json.go |  44 +++
 ragserver/ragserver-langchaingo/main.go | 160 ++++++++
 5 files changed, 741 insertions(+)
 create mode 100644 ragserver/ragserver-langchaingo/go.mod
 create mode 100644 ragserver/ragserver-langchaingo/go.sum
 create mode 100644 ragserver/ragserver-langchaingo/json.go
 create mode 100644 ragserver/ragserver-langchaingo/main.go

diff --git a/ragserver/README.md b/ragserver/README.md
index 2ccfdc0c..a5b209ee 100644
--- a/ragserver/README.md
+++ b/ragserver/README.md
@@ -31,6 +31,8 @@ Weaviate has the be installed locally; the easiest way to do so is by using
 
 * `ragserver`: uses the Google AI Go SDK directly for LLM calls and embeddings,
   and the Weaviate Go client library directly for interacting with Weaviate.
+* `ragserver-langchaingo`: uses [LangChain for Go](https://github.com/tmc/langchaingo)
+  to interact with Weaviate and Google's LLM and embedding models.
 
 ## Usage
 
diff --git a/ragserver/ragserver-langchaingo/go.mod b/ragserver/ragserver-langchaingo/go.mod
new file mode 100644
index 00000000..e17ac4d7
--- /dev/null
+++ b/ragserver/ragserver-langchaingo/go.mod
@@ -0,0 +1,70 @@
+module golang.org/x/example/ragserver/ragserver-langchaingo
+
+go 1.23.0
+
+require github.com/tmc/langchaingo v0.1.12
+
+require (
+	cloud.google.com/go v0.113.0 // indirect
+	cloud.google.com/go/ai v0.6.0 // indirect
+	cloud.google.com/go/aiplatform v1.67.0 // indirect
+	cloud.google.com/go/auth v0.4.1 // indirect
+	cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
+	cloud.google.com/go/compute/metadata v0.3.0 // indirect
+	cloud.google.com/go/iam v1.1.7 // indirect
+	cloud.google.com/go/longrunning v0.5.7 // indirect
+	cloud.google.com/go/vertexai v0.10.0 // indirect
+	github.com/PuerkitoBio/purell v1.1.1 // indirect
+	github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
+	github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
+	github.com/dlclark/regexp2 v1.10.0 // indirect
+	github.com/felixge/httpsnoop v1.0.4 // indirect
+	github.com/go-logr/logr v1.4.1 // indirect
+	github.com/go-logr/stdr v1.2.2 // indirect
+	github.com/go-openapi/analysis v0.21.2 // indirect
+	github.com/go-openapi/errors v0.22.0 // indirect
+	github.com/go-openapi/jsonpointer v0.19.6 // indirect
+	github.com/go-openapi/jsonreference v0.19.6 // indirect
+	github.com/go-openapi/loads v0.21.1 // indirect
+	github.com/go-openapi/spec v0.20.4 // indirect
+	github.com/go-openapi/strfmt v0.21.3 // indirect
+	github.com/go-openapi/swag v0.22.4 // indirect
+	github.com/go-openapi/validate v0.21.0 // indirect
+	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
+	github.com/golang/protobuf v1.5.4 // indirect
+	github.com/google/generative-ai-go v0.14.0 // indirect
+	github.com/google/s2a-go v0.1.7 // indirect
+	github.com/google/uuid v1.6.0 // indirect
+	github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
+	github.com/googleapis/gax-go/v2 v2.12.4 // indirect
+	github.com/josharian/intern v1.0.0 // indirect
+	github.com/mailru/easyjson v0.7.7 // indirect
+	github.com/mitchellh/mapstructure v1.5.0 // indirect
+	github.com/oklog/ulid v1.3.1 // indirect
+	github.com/pkg/errors v0.9.1 // indirect
+	github.com/pkoukk/tiktoken-go v0.1.6 // indirect
+	github.com/weaviate/weaviate v1.24.1 // indirect
+	github.com/weaviate/weaviate-go-client/v4 v4.13.1 // indirect
+	go.mongodb.org/mongo-driver v1.14.0 // indirect
+	go.opencensus.io v0.24.0 // indirect
+	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 // indirect
+	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect
+	go.opentelemetry.io/otel v1.26.0 // indirect
+	go.opentelemetry.io/otel/metric v1.26.0 // indirect
+	go.opentelemetry.io/otel/trace v1.26.0 // indirect
+	golang.org/x/crypto v0.23.0 // indirect
+	golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect
+	golang.org/x/net v0.25.0 // indirect
+	golang.org/x/oauth2 v0.20.0 // indirect
+	golang.org/x/sync v0.7.0 // indirect
+	golang.org/x/sys v0.20.0 // indirect
+	golang.org/x/text v0.15.0 // indirect
+	golang.org/x/time v0.5.0 // indirect
+	google.golang.org/api v0.180.0 // indirect
+	google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434 // indirect
+	google.golang.org/grpc v1.64.0 // indirect
+	google.golang.org/protobuf v1.34.1 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/ragserver/ragserver-langchaingo/go.sum b/ragserver/ragserver-langchaingo/go.sum
new file mode 100644
index 00000000..fe63ea82
--- /dev/null
+++ b/ragserver/ragserver-langchaingo/go.sum
@@ -0,0 +1,465 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.113.0 h1:g3C70mn3lWfckKBiCVsAshabrDg01pQ0pnX1MNtnMkA=
+cloud.google.com/go v0.113.0/go.mod h1:glEqlogERKYeePz6ZdkcLJ28Q2I6aERgDDErBg9GzO8=
+cloud.google.com/go/ai v0.6.0 h1:QWjb2UoaM15e51IMeLuIUFyWxooKOKDb66Mk47zZ2/g=
+cloud.google.com/go/ai v0.6.0/go.mod h1:6/mrRq6aJdK7MZH76ZvcMpESiAiha5aRvurmroiOrgI=
+cloud.google.com/go/aiplatform v1.67.0 h1:YWeqD4BjYwrmY4fa+isGcw0P81lJ3dKVxbWxdBchoiU=
+cloud.google.com/go/aiplatform v1.67.0/go.mod h1:s/sJ6btBEr6bKnrNWdK9ZgHCvwbZNdP90b3DDtxxw+Y=
+cloud.google.com/go/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg=
+cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro=
+cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
+cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
+cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
+cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
+cloud.google.com/go/iam v1.1.7 h1:z4VHOhwKLF/+UYXAJDFwGtNF0b6gjsW1Pk9Ml0U/IoM=
+cloud.google.com/go/iam v1.1.7/go.mod h1:J4PMPg8TtyurAUvSmPj8FF3EDgY1SPRZxcUGrn7WXGA=
+cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
+cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
+cloud.google.com/go/vertexai v0.10.0 h1:k157bLrtyajGtAAZnqdEn8lwFlUTG3BgHc7kvWbP/3s=
+cloud.google.com/go/vertexai v0.10.0/go.mod h1:w/Zb22QvOVvxx5CGM4fPzH3WA6gwUkId9juA7pigzFI=
+dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
+dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
+github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
+github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
+github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
+github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
+github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
+github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
+github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
+github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
+github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
+github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
+github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8=
+github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w=
+github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
+github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
+github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
+github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
+github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
+github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
+github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes=
+github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY=
+github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
+github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
+github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E=
+github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
+github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
+github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
+github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
+github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE=
+github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
+github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
+github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
+github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
+github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
+github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
+github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/go-openapi/analysis v0.21.2 h1:hXFrOYFHUAMQdu6zwAiKKJHJQ8kqZs1ux/ru1P1wLJU=
+github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY=
+github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
+github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
+github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
+github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w=
+github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE=
+github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
+github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
+github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
+github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
+github.com/go-openapi/loads v0.21.1 h1:Wb3nVZpdEzDTcly8S4HMkey6fjARRzb7iEaySimlDW0=
+github.com/go-openapi/loads v0.21.1/go.mod h1:/DtAMXXneXFjbQMGEtbamCZb+4x7eGwkvZCvBmwUG+g=
+github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
+github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
+github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg=
+github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k=
+github.com/go-openapi/strfmt v0.21.3 h1:xwhj5X6CjXEZZHMWy1zKJxvW9AfHC9pkyUjLvHtKG7o=
+github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg=
+github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
+github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
+github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
+github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
+github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
+github.com/go-openapi/validate v0.21.0 h1:+Wqk39yKOhfpLqNLEC0/eViCkzM5FVXVqrvt526+wcI=
+github.com/go-openapi/validate v0.21.0/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
+github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
+github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
+github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
+github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
+github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs=
+github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
+github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
+github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk=
+github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28=
+github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo=
+github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk=
+github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw=
+github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360=
+github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg=
+github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE=
+github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8=
+github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
+github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
+github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
+github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
+github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
+github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
+github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/generative-ai-go v0.14.0 h1:2GwFKXui9LmG+PukQwYk9KpJUIemmQ9NJ46BV9VIw38=
+github.com/google/generative-ai-go v0.14.0/go.mod h1:hOzbW3cB5hRV2x05McOwJS4GsqSluYwejjk5tSfb6YY=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
+github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
+github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
+github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
+github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg=
+github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI=
+github.com/goph/emperror v0.17.2 h1:yLapQcmEsO0ipe9p5TaN22djm3OFV/TfM/fcYP0/J18=
+github.com/goph/emperror v0.17.2/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic=
+github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
+github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
+github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
+github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
+github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
+github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
+github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
+github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
+github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
+github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
+github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
+github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
+github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
+github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
+github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
+github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
+github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
+github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
+github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
+github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
+github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
+github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
+github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg=
+github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU=
+github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
+github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
+github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
+github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/nikolalohinski/gonja v1.5.3 h1:GsA+EEaZDZPGJ8JtpeGN78jidhOlxeJROpqMT9fTj9c=
+github.com/nikolalohinski/gonja v1.5.3/go.mod h1:RmjwxNiXAEqcq1HeK5SSMmqFJvKOfTfXhkJv6YBtPa4=
+github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
+github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
+github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
+github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
+github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
+github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI=
+github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
+github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0=
+github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkoukk/tiktoken-go v0.1.6 h1:JF0TlJzhTbrI30wCvFuiw6FzP2+/bR+FIxUdgEAcUsw=
+github.com/pkoukk/tiktoken-go v0.1.6/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
+github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
+github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
+github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
+github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
+github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
+github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
+github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
+github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
+github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
+github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
+github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/testcontainers/testcontainers-go v0.31.0 h1:W0VwIhcEVhRflwL9as3dhY6jXjVCA27AkmbnZ+UTh3U=
+github.com/testcontainers/testcontainers-go v0.31.0/go.mod h1:D2lAoA0zUFiSY+eAflqK5mcUx/A5hrrORaEQrd0SefI=
+github.com/testcontainers/testcontainers-go/modules/weaviate v0.31.0 h1:iVJX9O12GHRhqPgIuz/eE8BsNEwyrUMJnWgduBt8quc=
+github.com/testcontainers/testcontainers-go/modules/weaviate v0.31.0/go.mod h1:WNc2XhLphiLdNJdjJZvUtRj08ThLY8FL60y7FQSJTPQ=
+github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
+github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
+github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
+github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
+github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
+github.com/tmc/langchaingo v0.1.12 h1:yXwSu54f3b1IKw0jJ5/DWu+qFVH1NBblwC0xddBzGJE=
+github.com/tmc/langchaingo v0.1.12/go.mod h1:cd62xD6h+ouk8k/QQFhOsjRYBSA1JJ5UVKXSIgm7Ni4=
+github.com/weaviate/weaviate v1.24.1 h1:Cl/NnqgFlNfyC7KcjFtETf1bwtTQPLF3oz5vavs+Jq0=
+github.com/weaviate/weaviate v1.24.1/go.mod h1:wcg1vJgdIQL5MWBN+871DFJQa+nI2WzyXudmGjJ8cG4=
+github.com/weaviate/weaviate-go-client/v4 v4.13.1 h1:7PuK/hpy6Q0b9XaVGiUg5OD1MI/eF2ew9CJge9XdBEE=
+github.com/weaviate/weaviate-go-client/v4 v4.13.1/go.mod h1:B2m6g77xWDskrCq1GlU6CdilS0RG2+YXEgzwXRADad0=
+github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
+github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
+github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
+github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
+github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
+github.com/yargevad/filepathx v1.0.0 h1:SYcT+N3tYGi+NvazubCNlvgIPbzAk7i7y2dwg3I5FYc=
+github.com/yargevad/filepathx v1.0.0/go.mod h1:BprfX/gpYNJHJfc35GjRRpVcwWXS89gGulUIU5tK3tA=
+github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
+github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
+github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
+go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg=
+go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng=
+go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8=
+go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
+go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
+go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
+go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 h1:A3SayB3rNyt+1S6qpI9mHPkeHTZbD7XILEqWnYZb2l0=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0/go.mod h1:27iA5uvhuRNmalO+iEUdVn5ZMj2qy10Mm+XRIpRmyuU=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc=
+go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs=
+go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4=
+go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30=
+go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4=
+go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA=
+go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0=
+go.starlark.net v0.0.0-20230302034142-4b1e35fe2254 h1:Ss6D3hLXTM0KobyBYEAygXzFfGcjnmfEJOBgSbemCtg=
+go.starlark.net v0.0.0-20230302034142-4b1e35fe2254/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
+golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
+golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
+golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
+golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
+golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
+golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
+golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
+golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
+golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
+golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.180.0 h1:M2D87Yo0rGBPWpo1orwfCLehUUL6E7/TYe5gvMQWDh4=
+google.golang.org/api v0.180.0/go.mod h1:51AiyoEg1MJPSZ9zvklA8VnRILPXxn1iVen9v25XHAE=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw=
+google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw=
+google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 h1:W5Xj/70xIA4x60O/IFyXivR5MGqblAb8R3w26pnD6No=
+google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8/go.mod h1:vPrPUTsDCYxXWjP7clS81mZ6/803D8K4iM9Ma27VKas=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434 h1:umK/Ey0QEzurTNlsV3R+MfxHAb78HCEX/IkuR+zH4WQ=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
+google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
+google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
+google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
+sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
diff --git a/ragserver/ragserver-langchaingo/json.go b/ragserver/ragserver-langchaingo/json.go
new file mode 100644
index 00000000..c0bb7ce6
--- /dev/null
+++ b/ragserver/ragserver-langchaingo/json.go
@@ -0,0 +1,44 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"encoding/json"
+	"fmt"
+	"mime"
+	"net/http"
+)
+
+// readRequestJSON expects req to have a JSON content type with a body that
+// contains a JSON-encoded value complying with the underlying type of target.
+// It populates target, or returns an error.
+func readRequestJSON(target any, req *http.Request) error {
+	contentType := req.Header.Get("Content-Type")
+	mediaType, _, err := mime.ParseMediaType(contentType)
+	if err != nil {
+		return err
+	}
+	if mediaType != "application/json" {
+		return fmt.Errorf("expect application/json Content-Type, got %s", mediaType)
+	}
+
+	dec := json.NewDecoder(req.Body)
+	dec.DisallowUnknownFields()
+	if err := dec.Decode(target); err != nil {
+		return err
+	}
+	return nil
+}
+
+// renderJSON renders 'v' as JSON and writes it as a response into w.
+func renderJSON(w http.ResponseWriter, v interface{}) {
+	js, err := json.Marshal(v)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+	w.Header().Set("Content-Type", "application/json")
+	w.Write(js)
+}
diff --git a/ragserver/ragserver-langchaingo/main.go b/ragserver/ragserver-langchaingo/main.go
new file mode 100644
index 00000000..86aad2c3
--- /dev/null
+++ b/ragserver/ragserver-langchaingo/main.go
@@ -0,0 +1,160 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Command ragserver is an HTTP server that implements RAG (Retrieval
+// Augmented Generation) using the Gemini model and Weaviate, which
+// are accessed using LangChainGo. See the accompanying README file for
+// additional details.
+package main
+
+import (
+	"cmp"
+	"context"
+	"fmt"
+	"log"
+	"net/http"
+	"os"
+	"strings"
+
+	"github.com/tmc/langchaingo/embeddings"
+	"github.com/tmc/langchaingo/llms"
+	"github.com/tmc/langchaingo/llms/googleai"
+	"github.com/tmc/langchaingo/schema"
+	"github.com/tmc/langchaingo/vectorstores/weaviate"
+)
+
+const generativeModelName = "gemini-1.5-flash"
+const embeddingModelName = "text-embedding-004"
+
+// This is a standard Go HTTP server. Server state is in the ragServer struct.
+// The `main` function connects to the required services (Weaviate and Google
+// AI), initializes the server state and registers HTTP handlers.
+func main() {
+	ctx := context.Background()
+	apiKey := os.Getenv("GEMINI_API_KEY")
+	geminiClient, err := googleai.New(ctx,
+		googleai.WithAPIKey(apiKey),
+		googleai.WithDefaultEmbeddingModel(embeddingModelName))
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	emb, err := embeddings.NewEmbedder(geminiClient)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	wvStore, err := weaviate.New(
+		weaviate.WithEmbedder(emb),
+		weaviate.WithScheme("http"),
+		weaviate.WithHost("localhost:"+cmp.Or(os.Getenv("WVPORT"), "9035")),
+		weaviate.WithIndexName("Document"),
+	)
+
+	server := &ragServer{
+		ctx:          ctx,
+		wvStore:      wvStore,
+		geminiClient: geminiClient,
+	}
+
+	mux := http.NewServeMux()
+	mux.HandleFunc("POST /add/", server.addDocumentsHandler)
+	mux.HandleFunc("GET /query/", server.queryHandler)
+
+	port := cmp.Or(os.Getenv("SERVERPORT"), "9020")
+	address := "localhost:" + port
+	log.Println("listening on", address)
+	log.Fatal(http.ListenAndServe(address, mux))
+}
+
+type ragServer struct {
+	ctx          context.Context
+	wvStore      weaviate.Store
+	geminiClient *googleai.GoogleAI
+}
+
+func (rs *ragServer) addDocumentsHandler(w http.ResponseWriter, req *http.Request) {
+	// Parse HTTP request from JSON.
+	type document struct {
+		Text string
+	}
+	type addRequest struct {
+		Documents []document
+	}
+	ar := &addRequest{}
+
+	err := readRequestJSON(ar, req)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+
+	// Store documents and their embeddings in weaviate
+	var wvDocs []schema.Document
+	for _, doc := range ar.Documents {
+		wvDocs = append(wvDocs, schema.Document{PageContent: doc.Text})
+	}
+	_, err = rs.wvStore.AddDocuments(rs.ctx, wvDocs)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+}
+
+func (rs *ragServer) queryHandler(w http.ResponseWriter, req *http.Request) {
+	// Parse HTTP request from JSON.
+	type queryRequest struct {
+		Content string
+	}
+	qr := &queryRequest{}
+	err := readRequestJSON(qr, req)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+
+	// Find the most similar documents.
+	docs, err := rs.wvStore.SimilaritySearch(rs.ctx, qr.Content, 3)
+	if err != nil {
+		http.Error(w, fmt.Errorf("similarity search: %w", err).Error(), http.StatusInternalServerError)
+		return
+	}
+	var docsContents []string
+	for _, doc := range docs {
+		docsContents = append(docsContents, doc.PageContent)
+	}
+
+	// Creata a RAG query for the LLM with the most relevant documents as
+	// context.
+	ragQuery := fmt.Sprintf(ragTemplateStr, qr.Content, strings.Join(docsContents, "\n"))
+	respText, err := llms.GenerateFromSinglePrompt(rs.ctx, rs.geminiClient, ragQuery, llms.WithModel(generativeModelName))
+	if err != nil {
+		log.Printf("calling generative model: %v", err.Error())
+		http.Error(w, "generative model error", http.StatusInternalServerError)
+		return
+	}
+
+	renderJSON(w, respText)
+}
+
+const ragTemplateStr = `
+I will ask you a question and will provide some additional context information.
+Assume this context information is factual and correct, as part of internal
+documentation.
+If the question relates to the context, answer it using the context.
+If the question does not relate to the context, answer it as normal.
+
+For example, let's say the context has nothing in it about tropical flowers;
+then if I ask you about tropical flowers, just answer what you know about them
+without referring to the context.
+
+For example, if the context does mention minerology and I ask you about that,
+provide information from the context along with general knowledge.
+
+Question:
+%s
+
+Context:
+%s
+`

From 88ba6f5abfa1105c375b9b34e7b146f809411706 Mon Sep 17 00:00:00 2001
From: Eli Bendersky 
Date: Tue, 27 Aug 2024 07:23:38 -0700
Subject: [PATCH 32/48] ragserver: add ragserver-genkit variant

Change-Id: I9e34c00ea00a01190e898c4de236061da1e1f55e
Reviewed-on: https://go-review.googlesource.com/c/example/+/608737
Auto-Submit: Eli Bendersky 
TryBot-Bypass: Eli Bendersky 
Reviewed-by: Jonathan Amsterdam 
Reviewed-by: Eli Bendersky 
Reviewed-by: Ian Lance Taylor 
---
 ragserver/README.md                |   4 +-
 ragserver/ragserver-genkit/go.mod  |  72 ++++++
 ragserver/ragserver-genkit/go.sum  | 346 +++++++++++++++++++++++++++++
 ragserver/ragserver-genkit/json.go |  41 ++++
 ragserver/ragserver-genkit/main.go | 183 +++++++++++++++
 ragserver/tests/query.sh           |   2 +-
 6 files changed, 646 insertions(+), 2 deletions(-)
 create mode 100644 ragserver/ragserver-genkit/go.mod
 create mode 100644 ragserver/ragserver-genkit/go.sum
 create mode 100644 ragserver/ragserver-genkit/json.go
 create mode 100644 ragserver/ragserver-genkit/main.go

diff --git a/ragserver/README.md b/ragserver/README.md
index a5b209ee..41c4d01f 100644
--- a/ragserver/README.md
+++ b/ragserver/README.md
@@ -23,7 +23,7 @@ Weaviate has the be installed locally; the easiest way to do so is by using
 /add/: POST {"documents": [{"text": "..."}, {"text": "..."}, ...]}
   response: OK (no body)
 
-/query/: GET {"content": "..."}
+/query/: POST {"content": "..."}
   response: model response as a string
 ```
 
@@ -33,6 +33,8 @@ Weaviate has the be installed locally; the easiest way to do so is by using
   and the Weaviate Go client library directly for interacting with Weaviate.
 * `ragserver-langchaingo`: uses [LangChain for Go](https://github.com/tmc/langchaingo)
   to interact with Weaviate and Google's LLM and embedding models.
+* `ragserver-genkit`: uses [Genkit Go](https://firebase.google.com/docs/genkit-go/get-started-go)
+  to interact with Weaviate and Google's LLM and embedding models.
 
 ## Usage
 
diff --git a/ragserver/ragserver-genkit/go.mod b/ragserver/ragserver-genkit/go.mod
new file mode 100644
index 00000000..53b3d59e
--- /dev/null
+++ b/ragserver/ragserver-genkit/go.mod
@@ -0,0 +1,72 @@
+module golang.org/x/example/ragserver/ragserver-genkit
+
+go 1.23.0
+
+require github.com/firebase/genkit/go v0.1.1
+
+require (
+	cloud.google.com/go v0.115.0 // indirect
+	cloud.google.com/go/ai v0.8.1-0.20240711230438-265963bd5b91 // indirect
+	cloud.google.com/go/auth v0.7.0 // indirect
+	cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
+	cloud.google.com/go/compute/metadata v0.4.0 // indirect
+	cloud.google.com/go/longrunning v0.5.9 // indirect
+	github.com/PuerkitoBio/purell v1.1.1 // indirect
+	github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
+	github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
+	github.com/bahlo/generic-list-go v0.2.0 // indirect
+	github.com/buger/jsonparser v1.1.1 // indirect
+	github.com/felixge/httpsnoop v1.0.4 // indirect
+	github.com/go-logr/logr v1.4.1 // indirect
+	github.com/go-logr/stdr v1.2.2 // indirect
+	github.com/go-openapi/analysis v0.21.2 // indirect
+	github.com/go-openapi/errors v0.22.0 // indirect
+	github.com/go-openapi/jsonpointer v0.19.5 // indirect
+	github.com/go-openapi/jsonreference v0.19.6 // indirect
+	github.com/go-openapi/loads v0.21.1 // indirect
+	github.com/go-openapi/spec v0.20.4 // indirect
+	github.com/go-openapi/strfmt v0.23.0 // indirect
+	github.com/go-openapi/swag v0.22.3 // indirect
+	github.com/go-openapi/validate v0.21.0 // indirect
+	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
+	github.com/golang/protobuf v1.5.4 // indirect
+	github.com/google/generative-ai-go v0.16.1-0.20240711222609-09946422abc6 // indirect
+	github.com/google/s2a-go v0.1.7 // indirect
+	github.com/google/uuid v1.6.0 // indirect
+	github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
+	github.com/googleapis/gax-go/v2 v2.12.5 // indirect
+	github.com/invopop/jsonschema v0.12.0 // indirect
+	github.com/josharian/intern v1.0.0 // indirect
+	github.com/mailru/easyjson v0.7.7 // indirect
+	github.com/mitchellh/mapstructure v1.5.0 // indirect
+	github.com/oklog/ulid v1.3.1 // indirect
+	github.com/pkg/errors v0.9.1 // indirect
+	github.com/weaviate/weaviate v1.26.0-rc.1 // indirect
+	github.com/weaviate/weaviate-go-client/v4 v4.15.0 // indirect
+	github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
+	github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
+	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
+	github.com/xeipuuv/gojsonschema v1.2.0 // indirect
+	go.mongodb.org/mongo-driver v1.14.0 // indirect
+	go.opencensus.io v0.24.0 // indirect
+	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 // indirect
+	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect
+	go.opentelemetry.io/otel v1.26.0 // indirect
+	go.opentelemetry.io/otel/metric v1.26.0 // indirect
+	go.opentelemetry.io/otel/sdk v1.26.0 // indirect
+	go.opentelemetry.io/otel/trace v1.26.0 // indirect
+	golang.org/x/crypto v0.25.0 // indirect
+	golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 // indirect
+	golang.org/x/net v0.27.0 // indirect
+	golang.org/x/oauth2 v0.21.0 // indirect
+	golang.org/x/sync v0.7.0 // indirect
+	golang.org/x/sys v0.22.0 // indirect
+	golang.org/x/text v0.16.0 // indirect
+	golang.org/x/time v0.5.0 // indirect
+	google.golang.org/api v0.188.0 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b // indirect
+	google.golang.org/grpc v1.65.0 // indirect
+	google.golang.org/protobuf v1.34.2 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/ragserver/ragserver-genkit/go.sum b/ragserver/ragserver-genkit/go.sum
new file mode 100644
index 00000000..65e37686
--- /dev/null
+++ b/ragserver/ragserver-genkit/go.sum
@@ -0,0 +1,346 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14=
+cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU=
+cloud.google.com/go/ai v0.8.1-0.20240711230438-265963bd5b91 h1:VA80iXvWirtF1jQK5BQd7MPHvHOE+UZ2v4AJCcChHqk=
+cloud.google.com/go/ai v0.8.1-0.20240711230438-265963bd5b91/go.mod h1:rVgd6oDdCDlN3mYqXqgE2nnzUblrwM/khbqLUXOJLeM=
+cloud.google.com/go/auth v0.7.0 h1:kf/x9B3WTbBUHkC+1VS8wwwli9TzhSt0vSTVBmMR8Ts=
+cloud.google.com/go/auth v0.7.0/go.mod h1:D+WqdrpcjmiCgWrXmLLxOVq1GACoE36chW6KXoEvuIw=
+cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
+cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
+cloud.google.com/go/compute/metadata v0.4.0 h1:vHzJCWaM4g8XIcm8kopr3XmDA4Gy/lblD3EhhSux05c=
+cloud.google.com/go/compute/metadata v0.4.0/go.mod h1:SIQh1Kkb4ZJ8zJ874fqVkslA29PRXuleyj6vOzlbK7M=
+cloud.google.com/go/longrunning v0.5.9 h1:haH9pAuXdPAMqHvzX0zlWQigXT7B0+CL4/2nXXdBo5k=
+cloud.google.com/go/longrunning v0.5.9/go.mod h1:HD+0l9/OOW0za6UWdKJtXoFAX/BGg/3Wj8p10NeWF7c=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
+github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
+github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
+github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
+github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
+github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
+github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
+github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/firebase/genkit/go v0.1.1 h1:Cg1cjRvfJeZdCgs5JXYOV2JhXw64SZroJv/mOnTlVWw=
+github.com/firebase/genkit/go v0.1.1/go.mod h1:XDbSDe348obNc/dqObxx7znYhANXoOQO9KdFamt1eS4=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
+github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-openapi/analysis v0.21.2 h1:hXFrOYFHUAMQdu6zwAiKKJHJQ8kqZs1ux/ru1P1wLJU=
+github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY=
+github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
+github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
+github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w=
+github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE=
+github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
+github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
+github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
+github.com/go-openapi/loads v0.21.1 h1:Wb3nVZpdEzDTcly8S4HMkey6fjARRzb7iEaySimlDW0=
+github.com/go-openapi/loads v0.21.1/go.mod h1:/DtAMXXneXFjbQMGEtbamCZb+4x7eGwkvZCvBmwUG+g=
+github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
+github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
+github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg=
+github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k=
+github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c=
+github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4=
+github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
+github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
+github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
+github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
+github.com/go-openapi/validate v0.21.0 h1:+Wqk39yKOhfpLqNLEC0/eViCkzM5FVXVqrvt526+wcI=
+github.com/go-openapi/validate v0.21.0/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
+github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
+github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
+github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
+github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
+github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs=
+github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
+github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
+github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk=
+github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28=
+github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo=
+github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk=
+github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw=
+github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360=
+github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg=
+github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE=
+github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8=
+github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
+github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
+github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
+github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
+github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
+github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
+github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/generative-ai-go v0.16.1-0.20240711222609-09946422abc6 h1:9nZYncjB1Wxlo7S+fM6YaXEWkK//zX9c0SExmzyctqA=
+github.com/google/generative-ai-go v0.16.1-0.20240711222609-09946422abc6/go.mod h1:JYolL13VG7j79kM5BtHz4qwONHkeJQzOCkKXnpqtS/E=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
+github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
+github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
+github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
+github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA=
+github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI=
+github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
+github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
+github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
+github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
+github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
+github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
+github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
+github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
+github.com/weaviate/weaviate v1.26.0-rc.1 h1:p+8Cw4VfAbevtf90/sEN+43IHMBtdUc5ZH3r4NZKVR8=
+github.com/weaviate/weaviate v1.26.0-rc.1/go.mod h1:o6nFEB4UozA+B2fnAWyF0HZqB2ab44pRhJHYwvxVyyA=
+github.com/weaviate/weaviate-go-client/v4 v4.15.0 h1:+gSKFLpy6iXTDNtjgYFOuCj0RY7F+sICefKpZarnOuA=
+github.com/weaviate/weaviate-go-client/v4 v4.15.0/go.mod h1:0aUsHXNfsdRfbO46t4Us5KUHqkhY+hlHevK6fBu72J4=
+github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
+github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
+github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
+github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
+github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
+github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
+github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
+github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
+github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
+go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg=
+go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng=
+go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
+go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
+go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
+go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 h1:A3SayB3rNyt+1S6qpI9mHPkeHTZbD7XILEqWnYZb2l0=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0/go.mod h1:27iA5uvhuRNmalO+iEUdVn5ZMj2qy10Mm+XRIpRmyuU=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc=
+go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs=
+go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4=
+go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30=
+go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4=
+go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8=
+go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs=
+go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA=
+go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
+golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
+golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 h1:6R2FC06FonbXQ8pK11/PDFY6N6LWlf9KlzibaCapmqc=
+golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
+golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
+golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
+golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
+golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
+golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
+golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
+golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.188.0 h1:51y8fJ/b1AaaBRJr4yWm96fPcuxSo0JcegXE3DaHQHw=
+google.golang.org/api v0.188.0/go.mod h1:VR0d+2SIiWOYG3r/jdm7adPW9hI2aRv9ETOSCQ9Beag=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0=
+google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b h1:04+jVzTs2XBnOZcPsLnmrTGqltqJbZQ1Ey26hjYdQQ0=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
+google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
+google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
+google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/ragserver/ragserver-genkit/json.go b/ragserver/ragserver-genkit/json.go
new file mode 100644
index 00000000..e19a6894
--- /dev/null
+++ b/ragserver/ragserver-genkit/json.go
@@ -0,0 +1,41 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"encoding/json"
+	"fmt"
+	"mime"
+	"net/http"
+)
+
+// readRequestJSON expects req to have a JSON content type with a body that
+// contains a JSON-encoded value complying with the underlying type of target.
+// It populates target, or returns an error.
+func readRequestJSON(req *http.Request, target any) error {
+	contentType := req.Header.Get("Content-Type")
+	mediaType, _, err := mime.ParseMediaType(contentType)
+	if err != nil {
+		return err
+	}
+	if mediaType != "application/json" {
+		return fmt.Errorf("expect application/json Content-Type, got %s", mediaType)
+	}
+
+	dec := json.NewDecoder(req.Body)
+	dec.DisallowUnknownFields()
+	return dec.Decode(target)
+}
+
+// renderJSON renders 'v' as JSON and writes it as a response into w.
+func renderJSON(w http.ResponseWriter, v any) {
+	js, err := json.Marshal(v)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+	w.Header().Set("Content-Type", "application/json")
+	w.Write(js)
+}
diff --git a/ragserver/ragserver-genkit/main.go b/ragserver/ragserver-genkit/main.go
new file mode 100644
index 00000000..fdddb634
--- /dev/null
+++ b/ragserver/ragserver-genkit/main.go
@@ -0,0 +1,183 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Command ragserver is an HTTP server that implements RAG (Retrieval
+// Augmented Generation) using the Gemini model and Weaviate, which
+// are accessed using the Genkit package. See the accompanying README file for
+// additional details.
+package main
+
+import (
+	"cmp"
+	"context"
+	"fmt"
+	"log"
+	"net/http"
+	"os"
+	"strings"
+
+	"github.com/firebase/genkit/go/ai"
+	"github.com/firebase/genkit/go/plugins/googleai"
+	"github.com/firebase/genkit/go/plugins/weaviate"
+)
+
+const generativeModelName = "gemini-1.5-flash"
+const embeddingModelName = "text-embedding-004"
+
+// This is a standard Go HTTP server. Server state is in the ragServer struct.
+// The `main` function connects to the required services (Weaviate and Google
+// AI), initializes the server state and registers HTTP handlers.
+func main() {
+	ctx := context.Background()
+	err := googleai.Init(ctx, &googleai.Config{
+		APIKey: os.Getenv("GEMINI_API_KEY"),
+	})
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	wvConfig := &weaviate.ClientConfig{
+		Scheme: "http",
+		Addr:   "localhost:" + cmp.Or(os.Getenv("WVPORT"), "9035"),
+	}
+	_, err = weaviate.Init(ctx, wvConfig)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	classConfig := &weaviate.ClassConfig{
+		Class:    "Document",
+		Embedder: googleai.Embedder(embeddingModelName),
+	}
+	indexer, retriever, err := weaviate.DefineIndexerAndRetriever(ctx, *classConfig)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	model := googleai.Model(generativeModelName)
+	if model == nil {
+		log.Fatal("unable to set up gemini-1.5-flash model")
+	}
+
+	server := &ragServer{
+		ctx:       ctx,
+		indexer:   indexer,
+		retriever: retriever,
+		model:     model,
+	}
+
+	mux := http.NewServeMux()
+	mux.HandleFunc("POST /add/", server.addDocumentsHandler)
+	mux.HandleFunc("POST /query/", server.queryHandler)
+
+	port := cmp.Or(os.Getenv("SERVERPORT"), "9020")
+	address := "localhost:" + port
+	log.Println("listening on", address)
+	log.Fatal(http.ListenAndServe(address, mux))
+}
+
+type ragServer struct {
+	ctx       context.Context
+	indexer   ai.Indexer
+	retriever ai.Retriever
+	model     ai.Model
+}
+
+func (rs *ragServer) addDocumentsHandler(w http.ResponseWriter, req *http.Request) {
+	// Parse HTTP request from JSON.
+	type document struct {
+		Text string
+	}
+	type addRequest struct {
+		Documents []document
+	}
+	ar := &addRequest{}
+	err := readRequestJSON(req, ar)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+
+	// Convert request documents into Weaviate documents used for embedding.
+	var wvDocs []*ai.Document
+	for _, doc := range ar.Documents {
+		wvDocs = append(wvDocs, ai.DocumentFromText(doc.Text, nil))
+	}
+
+	// Index the requested documents.
+	err = ai.Index(rs.ctx, rs.indexer, ai.WithIndexerDocs(wvDocs...))
+	if err != nil {
+		http.Error(w, fmt.Errorf("indexing: %w", err).Error(), http.StatusInternalServerError)
+		return
+	}
+}
+
+func (rs *ragServer) queryHandler(w http.ResponseWriter, req *http.Request) {
+	// Parse HTTP request from JSON.
+	type queryRequest struct {
+		Content string
+	}
+	qr := &queryRequest{}
+	err := readRequestJSON(req, qr)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusBadRequest)
+		return
+	}
+
+	// Find the most similar documents using the retriever.
+	resp, err := ai.Retrieve(rs.ctx,
+		rs.retriever,
+		ai.WithRetrieverDoc(ai.DocumentFromText(qr.Content, nil)),
+		ai.WithRetrieverOpts(&weaviate.RetrieverOptions{
+			Count: 3,
+		}))
+	if err != nil {
+		http.Error(w, fmt.Errorf("retrieval: %w", err).Error(), http.StatusInternalServerError)
+		return
+	}
+
+	var docsContents []string
+	for _, d := range resp.Documents {
+		docsContents = append(docsContents, d.Content[0].Text)
+	}
+
+	// Creata a RAG query for the LLM with the most relevant documents as
+	// context.
+	ragQuery := fmt.Sprintf(ragTemplateStr, qr.Content, strings.Join(docsContents, "\n"))
+	genResp, err := ai.Generate(rs.ctx, rs.model, ai.WithTextPrompt(ragQuery))
+	if err != nil {
+		log.Printf("calling generative model: %v", err.Error())
+		http.Error(w, "generative model error", http.StatusInternalServerError)
+		return
+	}
+
+	if len(genResp.Candidates) != 1 {
+		log.Printf("got %v candidates, expected 1", len(genResp.Candidates))
+		http.Error(w, "generative model error", http.StatusInternalServerError)
+		return
+	}
+
+	renderJSON(w, genResp.Text())
+}
+
+const ragTemplateStr = `
+I will ask you a question and will provide some additional context information.
+Assume this context information is factual and correct, as part of internal
+documentation.
+If the question relates to the context, answer it using the context.
+If the question does not relate to the context, answer it as normal.
+
+For example, let's say the context has nothing in it about tropical flowers;
+then if I ask you about tropical flowers, just answer what you know about them
+without referring to the context.
+
+For example, if the context does mention minerology and I ask you about that,
+provide information from the context along with general knowledge.
+
+Question:
+%s
+
+Context:
+%s
+`
diff --git a/ragserver/tests/query.sh b/ragserver/tests/query.sh
index 9013eb9c..3a0c53cd 100755
--- a/ragserver/tests/query.sh
+++ b/ragserver/tests/query.sh
@@ -18,7 +18,7 @@ PAYLOAD=$(echo "{\"content\": \"$QUERY_CONTENT\"}")
 
 # Send the request
 echo "$PAYLOAD" | tr -d "\n" | curl \
-    -X GET \
+    -X POST \
     -H 'Content-Type: application/json' \
     -d @- \
     http://localhost:9020/query/ | sed 's/\\n/\n/g'

From 0349f379ae1874a4e22b10c3ef5d082f2d75f315 Mon Sep 17 00:00:00 2001
From: Eli Bendersky 
Date: Fri, 30 Aug 2024 13:37:00 -0700
Subject: [PATCH 33/48] ragserver: fix other variants to be similar to
 ragserver-genkit

Apply suggestions from go.dev/cl/608737 to the other ragserver variants

Change-Id: I627cec1ff21c8f7c1b1d257b542435793ade680d
Reviewed-on: https://go-review.googlesource.com/c/example/+/609305
TryBot-Bypass: Eli Bendersky 
Auto-Submit: Eli Bendersky 
Reviewed-by: Ian Lance Taylor 
Reviewed-by: Eli Bendersky 
---
 ragserver/ragserver-langchaingo/json.go | 9 +++------
 ragserver/ragserver-langchaingo/main.go | 6 +++---
 ragserver/ragserver/json.go             | 9 +++------
 ragserver/ragserver/main.go             | 6 +++---
 4 files changed, 12 insertions(+), 18 deletions(-)

diff --git a/ragserver/ragserver-langchaingo/json.go b/ragserver/ragserver-langchaingo/json.go
index c0bb7ce6..e19a6894 100644
--- a/ragserver/ragserver-langchaingo/json.go
+++ b/ragserver/ragserver-langchaingo/json.go
@@ -14,7 +14,7 @@ import (
 // readRequestJSON expects req to have a JSON content type with a body that
 // contains a JSON-encoded value complying with the underlying type of target.
 // It populates target, or returns an error.
-func readRequestJSON(target any, req *http.Request) error {
+func readRequestJSON(req *http.Request, target any) error {
 	contentType := req.Header.Get("Content-Type")
 	mediaType, _, err := mime.ParseMediaType(contentType)
 	if err != nil {
@@ -26,14 +26,11 @@ func readRequestJSON(target any, req *http.Request) error {
 
 	dec := json.NewDecoder(req.Body)
 	dec.DisallowUnknownFields()
-	if err := dec.Decode(target); err != nil {
-		return err
-	}
-	return nil
+	return dec.Decode(target)
 }
 
 // renderJSON renders 'v' as JSON and writes it as a response into w.
-func renderJSON(w http.ResponseWriter, v interface{}) {
+func renderJSON(w http.ResponseWriter, v any) {
 	js, err := json.Marshal(v)
 	if err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
diff --git a/ragserver/ragserver-langchaingo/main.go b/ragserver/ragserver-langchaingo/main.go
index 86aad2c3..f679c44b 100644
--- a/ragserver/ragserver-langchaingo/main.go
+++ b/ragserver/ragserver-langchaingo/main.go
@@ -60,7 +60,7 @@ func main() {
 
 	mux := http.NewServeMux()
 	mux.HandleFunc("POST /add/", server.addDocumentsHandler)
-	mux.HandleFunc("GET /query/", server.queryHandler)
+	mux.HandleFunc("POST /query/", server.queryHandler)
 
 	port := cmp.Or(os.Getenv("SERVERPORT"), "9020")
 	address := "localhost:" + port
@@ -84,7 +84,7 @@ func (rs *ragServer) addDocumentsHandler(w http.ResponseWriter, req *http.Reques
 	}
 	ar := &addRequest{}
 
-	err := readRequestJSON(ar, req)
+	err := readRequestJSON(req, ar)
 	if err != nil {
 		http.Error(w, err.Error(), http.StatusBadRequest)
 		return
@@ -108,7 +108,7 @@ func (rs *ragServer) queryHandler(w http.ResponseWriter, req *http.Request) {
 		Content string
 	}
 	qr := &queryRequest{}
-	err := readRequestJSON(qr, req)
+	err := readRequestJSON(req, qr)
 	if err != nil {
 		http.Error(w, err.Error(), http.StatusBadRequest)
 		return
diff --git a/ragserver/ragserver/json.go b/ragserver/ragserver/json.go
index c0bb7ce6..e19a6894 100644
--- a/ragserver/ragserver/json.go
+++ b/ragserver/ragserver/json.go
@@ -14,7 +14,7 @@ import (
 // readRequestJSON expects req to have a JSON content type with a body that
 // contains a JSON-encoded value complying with the underlying type of target.
 // It populates target, or returns an error.
-func readRequestJSON(target any, req *http.Request) error {
+func readRequestJSON(req *http.Request, target any) error {
 	contentType := req.Header.Get("Content-Type")
 	mediaType, _, err := mime.ParseMediaType(contentType)
 	if err != nil {
@@ -26,14 +26,11 @@ func readRequestJSON(target any, req *http.Request) error {
 
 	dec := json.NewDecoder(req.Body)
 	dec.DisallowUnknownFields()
-	if err := dec.Decode(target); err != nil {
-		return err
-	}
-	return nil
+	return dec.Decode(target)
 }
 
 // renderJSON renders 'v' as JSON and writes it as a response into w.
-func renderJSON(w http.ResponseWriter, v interface{}) {
+func renderJSON(w http.ResponseWriter, v any) {
 	js, err := json.Marshal(v)
 	if err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
diff --git a/ragserver/ragserver/main.go b/ragserver/ragserver/main.go
index e55c66fe..f1a21f8d 100644
--- a/ragserver/ragserver/main.go
+++ b/ragserver/ragserver/main.go
@@ -52,7 +52,7 @@ func main() {
 
 	mux := http.NewServeMux()
 	mux.HandleFunc("POST /add/", server.addDocumentsHandler)
-	mux.HandleFunc("GET /query/", server.queryHandler)
+	mux.HandleFunc("POST /query/", server.queryHandler)
 
 	port := cmp.Or(os.Getenv("SERVERPORT"), "9020")
 	address := "localhost:" + port
@@ -77,7 +77,7 @@ func (rs *ragServer) addDocumentsHandler(w http.ResponseWriter, req *http.Reques
 	}
 	ar := &addRequest{}
 
-	err := readRequestJSON(ar, req)
+	err := readRequestJSON(req, ar)
 	if err != nil {
 		http.Error(w, err.Error(), http.StatusBadRequest)
 		return
@@ -127,7 +127,7 @@ func (rs *ragServer) queryHandler(w http.ResponseWriter, req *http.Request) {
 		Content string
 	}
 	qr := &queryRequest{}
-	err := readRequestJSON(qr, req)
+	err := readRequestJSON(req, qr)
 	if err != nil {
 		http.Error(w, err.Error(), http.StatusBadRequest)
 		return

From 4e46ff54a64bea9d2dec900072e2338425c2345a Mon Sep 17 00:00:00 2001
From: cui fliter 
Date: Fri, 22 Dec 2023 14:37:23 +0800
Subject: [PATCH 34/48] all: fix some typos

Change-Id: Id2b7fbb263d08103e17d2dc74454071fb3993586
Reviewed-on: https://go-review.googlesource.com/c/example/+/552396
Run-TryBot: shuang cui 
Auto-Submit: Dmitri Shuralyov 
TryBot-Result: Gopher Robot 
TryBot-Bypass: Dmitri Shuralyov 
Reviewed-by: Dmitri Shuralyov 
Reviewed-by: Michael Pratt 
Reviewed-by: Dmitri Shuralyov 
---
 internal/cmd/weave/weave.go  | 2 +-
 slog-handler-guide/README.md | 8 ++++----
 slog-handler-guide/guide.md  | 8 ++++----
 3 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/internal/cmd/weave/weave.go b/internal/cmd/weave/weave.go
index 07ed8bfe..91c06e4c 100644
--- a/internal/cmd/weave/weave.go
+++ b/internal/cmd/weave/weave.go
@@ -21,7 +21,7 @@
 //
 // is output, where PACKAGE is constructed from the module path, the
 // base name of the current directory, and the directory of FILENAME.
-// This caption can be supressed by putting "-" as the final word of the %include line.
+// This caption can be suppressed by putting "-" as the final word of the %include line.
 package main
 
 import (
diff --git a/slog-handler-guide/README.md b/slog-handler-guide/README.md
index 00eb1e5b..c8fd0d80 100644
--- a/slog-handler-guide/README.md
+++ b/slog-handler-guide/README.md
@@ -26,7 +26,7 @@ This document is maintained by Jonathan Amsterdam `jba@google.com`.
 
 The standard library’s `log/slog` package has a two-part design.
 A "frontend," implemented by the `Logger` type,
-gathers stuctured log information like a message, level, and attributes,
+gathers structured log information like a message, level, and attributes,
 and passes them to a "backend," an implementation of the `Handler` interface.
 The package comes with two built-in handlers that usually should be adequate.
 But you may need to write your own handler, and that is not always straightforward.
@@ -89,7 +89,7 @@ A logger's `WithGroup` method calls its handler's `WithGroup` method.
 
 We can now talk about the four `Handler` methods in detail.
 Along the way, we will write a handler that formats logs using a format
-reminsicent of YAML. It will display this log output call:
+reminiscent of YAML. It will display this log output call:
 
     logger.Info("hello", "key", 23)
 
@@ -138,7 +138,7 @@ func New(out io.Writer, opts *Options) *IndentHandler {
 ```
 
 We'll support only one option, the ability to set a minimum level in order to
-supress detailed log output.
+suppress detailed log output.
 Handlers should always declare this option to be a `slog.Leveler`.
 The `slog.Leveler` interface is implemented by both `Level` and `LevelVar`.
 A `Level` value is easy for the user to provide,
@@ -839,7 +839,7 @@ Beware of facile claims like "Unix writes are atomic"; the situation is a lot mo
 Some handlers have legitimate reasons for keeping state.
 For example, a handler might support a `SetLevel` method to change its configured level
 dynamically.
-Or it might output the time between sucessive calls to `Handle`,
+Or it might output the time between successive calls to `Handle`,
 which requires a mutable field holding the last output time.
 Synchronize all accesses to such fields, both reads and writes.
 
diff --git a/slog-handler-guide/guide.md b/slog-handler-guide/guide.md
index fb4664d3..4472bcaa 100644
--- a/slog-handler-guide/guide.md
+++ b/slog-handler-guide/guide.md
@@ -13,7 +13,7 @@ This document is maintained by Jonathan Amsterdam `jba@google.com`.
 
 The standard library’s `log/slog` package has a two-part design.
 A "frontend," implemented by the `Logger` type,
-gathers stuctured log information like a message, level, and attributes,
+gathers structured log information like a message, level, and attributes,
 and passes them to a "backend," an implementation of the `Handler` interface.
 The package comes with two built-in handlers that usually should be adequate.
 But you may need to write your own handler, and that is not always straightforward.
@@ -76,7 +76,7 @@ A logger's `WithGroup` method calls its handler's `WithGroup` method.
 
 We can now talk about the four `Handler` methods in detail.
 Along the way, we will write a handler that formats logs using a format
-reminsicent of YAML. It will display this log output call:
+reminiscent of YAML. It will display this log output call:
 
     logger.Info("hello", "key", 23)
 
@@ -100,7 +100,7 @@ and the `New` function that constructs it from an `io.Writer` and options:
 %include indenthandler1/indent_handler.go types -
 
 We'll support only one option, the ability to set a minimum level in order to
-supress detailed log output.
+suppress detailed log output.
 Handlers should always declare this option to be a `slog.Leveler`.
 The `slog.Leveler` interface is implemented by both `Level` and `LevelVar`.
 A `Level` value is easy for the user to provide,
@@ -557,7 +557,7 @@ Beware of facile claims like "Unix writes are atomic"; the situation is a lot mo
 Some handlers have legitimate reasons for keeping state.
 For example, a handler might support a `SetLevel` method to change its configured level
 dynamically.
-Or it might output the time between sucessive calls to `Handle`,
+Or it might output the time between successive calls to `Handle`,
 which requires a mutable field holding the last output time.
 Synchronize all accesses to such fields, both reads and writes.
 

From f3623ff43ac28483711860daca3000d84336024a Mon Sep 17 00:00:00 2001
From: Dan Peterson 
Date: Sat, 14 Sep 2024 17:26:22 -0300
Subject: [PATCH 35/48] ragserver: fix typos

Change-Id: Ibbaf613d84b76e78eaf001d082b505e24885d4d6
Reviewed-on: https://go-review.googlesource.com/c/example/+/613276
Reviewed-by: Ian Lance Taylor 
TryBot-Bypass: Ian Lance Taylor 
Auto-Submit: Ian Lance Taylor 
Reviewed-by: Eli Bendersky 
TryBot-Result: Gopher Robot 
Run-TryBot: Dan Peterson 
---
 ragserver/ragserver-genkit/main.go      | 2 +-
 ragserver/ragserver-langchaingo/main.go | 2 +-
 ragserver/ragserver/main.go             | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/ragserver/ragserver-genkit/main.go b/ragserver/ragserver-genkit/main.go
index fdddb634..db6fb0d9 100644
--- a/ragserver/ragserver-genkit/main.go
+++ b/ragserver/ragserver-genkit/main.go
@@ -142,7 +142,7 @@ func (rs *ragServer) queryHandler(w http.ResponseWriter, req *http.Request) {
 		docsContents = append(docsContents, d.Content[0].Text)
 	}
 
-	// Creata a RAG query for the LLM with the most relevant documents as
+	// Create a RAG query for the LLM with the most relevant documents as
 	// context.
 	ragQuery := fmt.Sprintf(ragTemplateStr, qr.Content, strings.Join(docsContents, "\n"))
 	genResp, err := ai.Generate(rs.ctx, rs.model, ai.WithTextPrompt(ragQuery))
diff --git a/ragserver/ragserver-langchaingo/main.go b/ragserver/ragserver-langchaingo/main.go
index f679c44b..6cbcf889 100644
--- a/ragserver/ragserver-langchaingo/main.go
+++ b/ragserver/ragserver-langchaingo/main.go
@@ -125,7 +125,7 @@ func (rs *ragServer) queryHandler(w http.ResponseWriter, req *http.Request) {
 		docsContents = append(docsContents, doc.PageContent)
 	}
 
-	// Creata a RAG query for the LLM with the most relevant documents as
+	// Create a RAG query for the LLM with the most relevant documents as
 	// context.
 	ragQuery := fmt.Sprintf(ragTemplateStr, qr.Content, strings.Join(docsContents, "\n"))
 	respText, err := llms.GenerateFromSinglePrompt(rs.ctx, rs.geminiClient, ragQuery, llms.WithModel(generativeModelName))
diff --git a/ragserver/ragserver/main.go b/ragserver/ragserver/main.go
index f1a21f8d..3e2d80ac 100644
--- a/ragserver/ragserver/main.go
+++ b/ragserver/ragserver/main.go
@@ -161,7 +161,7 @@ func (rs *ragServer) queryHandler(w http.ResponseWriter, req *http.Request) {
 		return
 	}
 
-	// Creata a RAG query for the LLM with the most relevant documents as
+	// Create a RAG query for the LLM with the most relevant documents as
 	// context.
 	ragQuery := fmt.Sprintf(ragTemplateStr, qr.Content, strings.Join(contents, "\n"))
 	resp, err := rs.genModel.GenerateContent(rs.ctx, genai.Text(ragQuery))

From 1a5e218e545549299aaf35cae608b33838e220d7 Mon Sep 17 00:00:00 2001
From: Ian Lance Taylor 
Date: Tue, 24 Sep 2024 11:17:12 -0700
Subject: [PATCH 36/48] ragserver-langchaingo: update to newer version of grpc

I ran
    go get go get google.golang.org/grpc
    go mod tidy

This turns off a GitHub Dependabot alert.
Version 1.64.0 is reported to have a minor vulnerability.

Change-Id: I0b7d05edc9001cf85bd5e7d9dc4bc534261f355a
Reviewed-on: https://go-review.googlesource.com/c/example/+/615555
Reviewed-by: Eli Bendersky 
Reviewed-by: Ian Lance Taylor 
LUCI-TryBot-Result: Go LUCI 
Commit-Queue: Ian Lance Taylor 
Auto-Submit: Ian Lance Taylor 
---
 ragserver/ragserver-langchaingo/go.mod | 22 +++++------
 ragserver/ragserver-langchaingo/go.sum | 52 +++++++++++++-------------
 2 files changed, 37 insertions(+), 37 deletions(-)

diff --git a/ragserver/ragserver-langchaingo/go.mod b/ragserver/ragserver-langchaingo/go.mod
index e17ac4d7..24be06be 100644
--- a/ragserver/ragserver-langchaingo/go.mod
+++ b/ragserver/ragserver-langchaingo/go.mod
@@ -10,7 +10,7 @@ require (
 	cloud.google.com/go/aiplatform v1.67.0 // indirect
 	cloud.google.com/go/auth v0.4.1 // indirect
 	cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
-	cloud.google.com/go/compute/metadata v0.3.0 // indirect
+	cloud.google.com/go/compute/metadata v0.5.0 // indirect
 	cloud.google.com/go/iam v1.1.7 // indirect
 	cloud.google.com/go/longrunning v0.5.7 // indirect
 	cloud.google.com/go/vertexai v0.10.0 // indirect
@@ -52,19 +52,19 @@ require (
 	go.opentelemetry.io/otel v1.26.0 // indirect
 	go.opentelemetry.io/otel/metric v1.26.0 // indirect
 	go.opentelemetry.io/otel/trace v1.26.0 // indirect
-	golang.org/x/crypto v0.23.0 // indirect
+	golang.org/x/crypto v0.26.0 // indirect
 	golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect
-	golang.org/x/net v0.25.0 // indirect
-	golang.org/x/oauth2 v0.20.0 // indirect
-	golang.org/x/sync v0.7.0 // indirect
-	golang.org/x/sys v0.20.0 // indirect
-	golang.org/x/text v0.15.0 // indirect
+	golang.org/x/net v0.28.0 // indirect
+	golang.org/x/oauth2 v0.22.0 // indirect
+	golang.org/x/sync v0.8.0 // indirect
+	golang.org/x/sys v0.24.0 // indirect
+	golang.org/x/text v0.17.0 // indirect
 	golang.org/x/time v0.5.0 // indirect
 	google.golang.org/api v0.180.0 // indirect
 	google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda // indirect
-	google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434 // indirect
-	google.golang.org/grpc v1.64.0 // indirect
-	google.golang.org/protobuf v1.34.1 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
+	google.golang.org/grpc v1.67.0 // indirect
+	google.golang.org/protobuf v1.34.2 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/ragserver/ragserver-langchaingo/go.sum b/ragserver/ragserver-langchaingo/go.sum
index fe63ea82..7faf6bb6 100644
--- a/ragserver/ragserver-langchaingo/go.sum
+++ b/ragserver/ragserver-langchaingo/go.sum
@@ -9,8 +9,8 @@ cloud.google.com/go/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg=
 cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro=
 cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
 cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
-cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
-cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
+cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
+cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
 cloud.google.com/go/iam v1.1.7 h1:z4VHOhwKLF/+UYXAJDFwGtNF0b6gjsW1Pk9Ml0U/IoM=
 cloud.google.com/go/iam v1.1.7/go.mod h1:J4PMPg8TtyurAUvSmPj8FF3EDgY1SPRZxcUGrn7WXGA=
 cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
@@ -343,16 +343,16 @@ golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaE
 golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
-golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
+golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
+golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
 golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
-golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
+golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -361,11 +361,11 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
-golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
+golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
+golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
-golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
+golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -373,8 +373,8 @@ golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
-golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -388,16 +388,16 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
-golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
+golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
-golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
+golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
 golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
 golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -409,8 +409,8 @@ golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3
 golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
-golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
+golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
+golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/api v0.180.0 h1:M2D87Yo0rGBPWpo1orwfCLehUUL6E7/TYe5gvMQWDh4=
 google.golang.org/api v0.180.0/go.mod h1:51AiyoEg1MJPSZ9zvklA8VnRILPXxn1iVen9v25XHAE=
@@ -421,17 +421,17 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
 google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw=
 google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw=
-google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 h1:W5Xj/70xIA4x60O/IFyXivR5MGqblAb8R3w26pnD6No=
-google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8/go.mod h1:vPrPUTsDCYxXWjP7clS81mZ6/803D8K4iM9Ma27VKas=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434 h1:umK/Ey0QEzurTNlsV3R+MfxHAb78HCEX/IkuR+zH4WQ=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM=
+google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8=
+google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
-google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
-google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
+google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw=
+google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -441,8 +441,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
-google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
-google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
+google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

From d7b0ac1278591aea848a99258ccfdee8e4d454c1 Mon Sep 17 00:00:00 2001
From: Alan Donovan 
Date: Mon, 14 Oct 2024 11:42:36 -0400
Subject: [PATCH 37/48] gotypes tutorial: update for Aliases

This CL updates the tutorial to mention (materialized) aliases
and the upcoming parameterized aliases of go1.24.
We don't yet attempt a thorough treatment of generics.

It also corrects a number of other obsolete or inaccurate
statements.

Change-Id: I2bbdc1a9000eb5bd214620c511d7e60692b4d9de
Reviewed-on: https://go-review.googlesource.com/c/example/+/620075
LUCI-TryBot-Result: Go LUCI 
Reviewed-by: Robert Findley 
---
 gotypes/README.md   | 226 ++++++++++++++++++++++++++++++--------------
 gotypes/go-types.md | 223 +++++++++++++++++++++++++++++--------------
 2 files changed, 309 insertions(+), 140 deletions(-)

diff --git a/gotypes/README.md b/gotypes/README.md
index 9923e108..350fbc68 100644
--- a/gotypes/README.md
+++ b/gotypes/README.md
@@ -22,8 +22,11 @@ This document is maintained by Alan Donovan `adonovan@google.com`.
 	1. [Struct Types](#struct-types)
 	1. [Tuple Types](#tuple-types)
 	1. [Function and Method Types](#function-and-method-types)
+	1. [Alias Types](#alias-types)
 	1. [Named Types](#named-types)
 	1. [Interface Types](#interface-types)
+	1. [TypeParam types](#typeparam-types)
+	1. [Union types](#union-types)
 	1. [TypeAndValue](#typeandvalue)
 1. [Selections](#selections)
 1. [Ids](#ids)
@@ -103,9 +106,6 @@ type, or has an inappropriate type for its context; this is known as
 _type deduction_.
 Third, for every constant expression in the program, it determines the
 value of that constant; this is known as _constant evaluation_.
-
-
-
 Superficially, it appears that these three processes could be done
 sequentially, in the order above, but perhaps surprisingly, they must
 be done together.
@@ -115,9 +115,6 @@ Conversely, the type of an expression may depend on the value of a
 constant, since array types contain constants.
 As a result, type deduction and constant evaluation must be done
 together.
-
-
-
 As another example, we cannot resolve the identifier `k` in the composite
 literal `T{k: 0}` until we know whether `T` is a struct type.
 If it is, then `k` must be found among `T`'s fields.
@@ -264,7 +261,7 @@ Scope:   package "cmd/hello" scope 0x820533590 {
 
 A package's `Path`, such as `"encoding/json"`, is the string
 by which import declarations identify it.
-It is unique within a `$GOPATH` workspace,
+It is unique within a workspace,
 and for published packages it must be globally unique.
 
 
@@ -282,7 +279,7 @@ which provides access to all the named entities or
 [_objects_](#objects) declared at package level.
 `Imports` returns the set of packages directly imported by this
 one, and  may be useful for computing dependencies
-([Initialization Order](#initialization-order)).
+(see [Initialization Order](#initialization-order)).
 
 
 
@@ -336,12 +333,8 @@ offset, though usually we just call its `String` method:
 	fmt.Println(fset.Position(obj.Pos())) // "hello.go:10:6"
 
 
-Not all objects carry position information.
-Since the file format for compiler export data ([Imports](#imports))
-does not record position information, calling `Pos` on an object
-imported from such a file returns zero, also known as
-`token.NoPos`.
-
+Objects for predeclared functions and types such as `len` and `int`
+do not have a valid (non-zero) position: `!obj.Pos().IsValid()`.
 
 
 There are eight kinds of objects in the Go type checker.
@@ -367,14 +360,22 @@ possible types, and we commonly use a type switch to distinguish them.
 	       | *Nil          // predeclared nil
 
 
-`Object`s are canonical.
-That is, two `Object`s `x` and `y` denote the same
-entity if and only if `x==y`.
+
+Objects are canonical.
+That is, two Objects `x` and `y` denote the same entity if and only if `x==y`.
+(This is generally true but beware that parameterized types complicate matters; see
+https://github.com/golang/exp/tree/master/typeparams/example for details.)
+
 Object identity is significant, and objects are routinely compared by
 the addresses of the underlying pointers.
-Although a package-level object is uniquely identified by its name
-and enclosing package, for other objects there is no simple way to
-obtain a string that uniquely identifies it.
+A package-level object (func/var/const/type) can be uniquely
+identified by its name and enclosing package.
+The [`golang.org/x/tools/go/types/objectpath`](https://pkg.go.dev/golang.org/x/tools/go/types/objectpath)
+package defines a naming scheme for objects that are
+[exported](#imports) from their package or are unexported but form part of the
+type of an exported object.
+But for most objects, including all function-local objects,
+there is no simple way to obtain a string that uniquely identifies it.
 
 
 
@@ -409,7 +410,7 @@ And some kinds of objects have methods in addition to those required by the
 
 
 `(*Func).Scope` returns the [lexical block](#scopes)
-containing the function's parameters, results,
+containing the function's type parameters, parameters, results,
 and other local declarations.
 `(*Var).IsField` distinguishes struct fields from ordinary
 variables, and `(*Var).Anonymous` discriminates named fields like
@@ -417,10 +418,12 @@ the one in `struct{T T}` from anonymous fields like the one in `struct{T}`.
 `(*Const).Val` returns the value of a named [constant](#constants).
 
 
-`(*TypeName).IsAlias`, introduced in Go 1.9, reports whether the
-type name is simply an alias for a type (as in `type I = int`),
-as opposed to a definition of a [`Named`](#named-types) type, as
-in `type Celsius float64`.
+`(*TypeName).IsAlias` reports whether the type name declares an alias
+for an existing type (as in `type I = int`), as opposed to defining a new
+[`Named`](#named-types) type, as in `type Celsius float64`.
+(Most `TypeName`s for which `IsAlias()` is true have a `Type()` of
+type `*types.Alias`, but `IsAlias()` is also true for the predeclared
+`byte` and `rune` types, which are aliases for `uint8` and `int32`.)
 
 
 `(*PkgName).Imported` returns the package (for instance,
@@ -436,9 +439,9 @@ We'll look more closely at this in [Imports](#imports).
 All relationships between the syntax trees (`ast.Node`s) and type
 checker data structures such as `Object`s and `Type`s are
 stored in mappings outside the syntax tree itself.
-Be aware that the `go/ast` package also defines a type called
-`Object` that resembles---and predates---the type checker's
-`Object`, and that `ast.Object`s are held directly by
+Be aware that the `go/ast` package also defines an older deprecated
+type called `Object` that resembles---and predates---the type
+checker's `Object`, and that `ast.Object`s are held directly by
 identifiers in the AST.
 They are created by the parser, which has a necessarily limited view
 of the package, so the information they represent is at best partial and
@@ -974,7 +977,7 @@ Here is the interface:
 	}
 
 
-And here are the eleven concrete types that satisfy it:
+And here are the 14 concrete types that satisfy it:
 
 
 	Type = *Basic
@@ -986,12 +989,17 @@ And here are the eleven concrete types that satisfy it:
 	     | *Struct
 	     | *Tuple
 	     | *Signature
+	     | *Alias
 	     | *Named
 	     | *Interface
+	     | *Union
+	     | *TypeParam
 
 
 With the exception of `Named` types, instances of `Type` are
 not canonical.
+(Even for `Named` types, parameterized types complicate matters; see
+https://github.com/golang/exp/tree/master/typeparams/example.)
 That is, it is usually a mistake to compare types using `t1==t2`
 since this equivalence is not the same as the
 [type identity relation](https://golang.org/ref/spec#Type_identity)
@@ -1052,8 +1060,8 @@ basic type this is.
 The kinds `Bool`, `String`, `Int16`, and so on,
 represent the corresponding predeclared boolean, string, or numeric
 types.
-There are two synonyms: `Byte` is equivalent to `Uint8`
-and `Rune` is equivalent to `Int32`.
+There are two aliases: `Byte` is an alias for `Uint8`
+and `Rune` is an alias for `Int32`.
 The kind `UnsafePointer` represents `unsafe.Pointer`.
 The kinds `UntypedBool`, `UntypedInt` and so on represent
 the six kinds of "untyped" constant types: boolean, integer, rune,
@@ -1082,21 +1090,20 @@ modify it.
 
 
 A few minor subtleties:
-According to the Go spec, pre-declared types such as `int` are
-named types for the purposes of assignability, even though the type
-checker does not represent them using `Named`.
-And `unsafe.Pointer` is a pointer type for the purpose of
-determining whether the receiver type of a method is legal, even
-though the type checker does not represent it using `Pointer`.
-
 
+- According to the Go spec, pre-declared types such as `int` are
+  named types for the purposes of assignability, even though the type
+  checker does not represent them using `Named`.
 
-The "untyped" types are usually only ascribed to constant expressions,
-but there is one exception.
-A comparison `x==y` has type "untyped bool", so the result of
-this expression may be assigned to a variable of type `bool` or
-any other named boolean type.
+- `unsafe.Pointer` is a pointer type for the purpose of
+  determining whether the receiver type of a method is legal, even
+  though the type checker does not represent it using `Pointer`.
 
+- The "untyped" types are usually only ascribed to constant expressions,
+  but there is one exception.
+  A comparison `x==y` has type "untyped bool", so the result of
+  this expression may be assigned to a variable of type `bool` or
+  any other named boolean type.
 
 
 ## Simple Composite Types
@@ -1262,21 +1269,73 @@ These types are recorded during type checking for later use
 
 
 
-## Named Types
+## Alias Types
+
+Type declarations come in two forms, aliases and defined types.
 
+Aliases, though introduced only in Go 1.9 and not very common, are
+simplest, so we'll present them first and explain defined types in
+the next section ("Named Types").
 
-Type declarations come in two forms.
-The simplest kind, introduced in Go 1.9,
-merely declares a (possibly alternative) name for an existing type.
-Type names used in this way are informally called _type aliases_.
-For example, this declaration lets you use the type
-`Dictionary` as an alias for `map[string]string`:
+An alias type declaration declares an alternative name for an existing
+type. For example, this declaration lets you use the type `Dictionary`
+as a synonym for `map[string]string`:
 
 	type Dictionary = map[string]string
 
-The declaration creates a `TypeName` object for `Dictionary`.  The
-object's `IsAlias` method returns true, and its `Type` method returns
-a `Map` type that represents `map[string]string`.
+The declaration creates a `TypeName` object for `Dictionary`.
+The object's `IsAlias` method returns true,
+and its `Type` method returns an `Alias`:
+
+        type Alias struct{ ... }
+	func (a *Alias) Obj() *TypeName
+	func (a *Alias) Origin() *Alias
+	func (a *Alias) Rhs() Type
+	func (a *Alias) SetTypeParams(tparams []*TypeParam)
+	func (a *Alias) TypeArgs() *TypeList
+	func (a *Alias) TypeParams() *TypeParamList
+
+The type on the right-hand side of an alias declaration,
+such as `map[string]string` in the example above,
+can be accessed using the `Rhs()` method.
+The `types.Unalias(t)` helper function recursively applies `Rhs`,
+removing all `Alias` types from the operand t and returning the
+outermost non-alias type.
+
+The `Obj` method returns the declaring `TypeName` object, such as
+`Dictionary`; it provides the name, position, and other properties of
+the declaration. Conversely, the `TypeName` object's `Type` method
+returns the `Alias` type.
+
+Starting with Go 1.24, alias types may have type parameters.
+For example, this declaration creates an Alias type with
+a type parameter:
+
+	type Set[T comparable] = map[T]bool
+
+Each instantiation such as `Set[string]` is identical to the
+corresponding instantiation of the alias' right-hand side type, such
+as `map[string]bool`.
+
+The remaining methods--Origin, SetTypeParams, TypeArgs,
+TypeParams--are all concerned with type parameters. For now, see
+https://github.com/golang/exp/tree/master/typeparams/example.
+
+Prior to Go 1.22, aliases were not materialized as `Alias` types:
+each reference to an alias type such as `Dictionary` would be
+immediately replaced by its right-hand side type, leaving no
+indication in the output of the type checker that an alias was
+present.
+By materializing alias types, optionally in Go 1.22 and by default
+starting in Go 1.23, we can more faithfully record the structure of
+the program, which improves the quality of diagnostic messages and
+enables certain analyses and code transformations. And, crucially, it
+enabled the addition of parameterized aliases in Go 1.24.)
+
+
+
+## Named Types
+
 
 
 The second form of type declaration, and the only kind prior to Go
@@ -1292,10 +1351,11 @@ from any other type, including `float64`.  The declaration binds the
 
 Since Go 1.9, the Go language specification has used the term _defined
 types_ instead of named types;
-the essential property of a defined type is not that it has a name,
+the essential property of a defined type is not that it has a name
+(aliases and type parameters also have names)
 but that it is a distinct type with its own method set.
 However, the type checker API predates that
-change and instead calls defined types "named" types.
+change and instead calls defined types `Named` types.
 
 	type Named struct{ ... }
 	func (*Named) NumMethods() int
@@ -1321,8 +1381,8 @@ methods than this list.  We'll return to this in [Method Sets](#method-sets).
 
 
 Every `Type` has an `Underlying` method, but for all of them
-except `*Named`, it is simply the identity function.
-For a named type, `Underlying` returns its underlying type, which
+except `*Named` and `*Alias`, it is simply the identity function.
+For a named or alias type, `Underlying` returns its underlying type, which
 is always an unnamed type.
 Thus `Underlying` returns `int` for both `T` and
 `U` below.
@@ -1335,14 +1395,13 @@ Thus `Underlying` returns `int` for both `T` and
 Clients of the type checker often use type assertions or type switches
 with a `Type` operand.
 When doing so, it is often necessary to switch on the type that
-_underlies_ the type of interest, and failure to do so may be a
-bug.
+underlies the type of interest, and failure to do so may be a bug.
 
 This is a common pattern:
 
 
 	// handle types of composite literal
-	switch u := t.Underlying().(type) {
+	switch u := t.Underlying().(type) {  // remove any *Named and *Alias types
 	case *Struct:        // ...
 	case *Map:           // ...
 	case *Array, *Slice: // ...
@@ -1423,6 +1482,36 @@ interface `v`, then the type assertion is not legal, as in this example:
 
 
 
+
+## TypeParam types
+
+
+A `TypeParam` is the type of a type parameter.
+For example, the type of the variable `x` in the `identity` function
+below is a `TypeParam`:
+
+    func identity[T any](x T) T { return x }
+
+As with `Alias` and `Named` types, each `TypeParam` has an associated
+`TypeName` object that provides its name, position, and other
+properties of the declaration.
+
+See https://github.com/golang/exp/tree/master/typeparams/example
+for a more thorough treatment of parameterized types.
+
+
+
+
+## Union types
+
+A `Union` is the type of type-parameter constraint of the form `func
+f[T int | string]`.
+
+See https://github.com/golang/exp/tree/master/typeparams/example
+for a more thorough treatment of parameterized types.
+
+
+
 ## TypeAndValue
 
 
@@ -2264,10 +2353,9 @@ compiler file formats, and so on.
 	}
 
 
-Most of our examples used the simplest `Importer` implementation,
+Most of our examples used the trivial `Importer` implementation,
 `importer.Default()`, provided by the `go/importer` package.
-This importer looks in `$GOROOT` and `$GOPATH` for `.a`
-files written by the compiler (`gc` or `gccgo`)
+This importer looks for `.a` files written by the compiler
 that was used to build the program.
 In addition to object code, these files contain _export data_,
 that is, a description of all the objects declared by the package, and
@@ -2279,13 +2367,11 @@ transitive dependency.
 
 
 Compiler export data is compact and efficient to locate, load, and
-parse, but it has several shortcomings.
-First, it does not contain position information for imported
-objects, reducing the quality of certain diagnostic messages.
-Second, it does not contain complete syntax trees nor semantic information
-about the contents of function bodies, so it is not suitable for
-interprocedural analyses.
-Third, compiler object data may be stale.  Nothing detects or ensures
+parse, but it has some shortcomings.
+First, it does not contain complete syntax trees nor semantic
+information about the bodies of all functions, so it is not
+suitable for interprocedural analyses.
+Second, compiler object data may be stale.  Nothing detects or ensures
 that the object files are more recent than the source files from which
 they were derived.
 Generally, object data for standard packages is likely to be
diff --git a/gotypes/go-types.md b/gotypes/go-types.md
index ce5a430d..9577ea6e 100644
--- a/gotypes/go-types.md
+++ b/gotypes/go-types.md
@@ -79,9 +79,6 @@ type, or has an inappropriate type for its context; this is known as
 _type deduction_.
 Third, for every constant expression in the program, it determines the
 value of that constant; this is known as _constant evaluation_.
-
-
-
 Superficially, it appears that these three processes could be done
 sequentially, in the order above, but perhaps surprisingly, they must
 be done together.
@@ -91,9 +88,6 @@ Conversely, the type of an expression may depend on the value of a
 constant, since array types contain constants.
 As a result, type deduction and constant evaluation must be done
 together.
-
-
-
 As another example, we cannot resolve the identifier `k` in the composite
 literal `T{k: 0}` until we know whether `T` is a struct type.
 If it is, then `k` must be found among `T`'s fields.
@@ -180,7 +174,7 @@ Finally, the program prints the attributes of the package, shown below.
 
 A package's `Path`, such as `"encoding/json"`, is the string
 by which import declarations identify it.
-It is unique within a `$GOPATH` workspace,
+It is unique within a workspace,
 and for published packages it must be globally unique.
 
 
@@ -198,7 +192,7 @@ which provides access to all the named entities or
 [_objects_](#objects) declared at package level.
 `Imports` returns the set of packages directly imported by this
 one, and  may be useful for computing dependencies
-([Initialization Order](#initialization-order)).
+(see [Initialization Order](#initialization-order)).
 
 
 
@@ -252,12 +246,8 @@ offset, though usually we just call its `String` method:
 	fmt.Println(fset.Position(obj.Pos())) // "hello.go:10:6"
 
 
-Not all objects carry position information.
-Since the file format for compiler export data ([Imports](#imports))
-does not record position information, calling `Pos` on an object
-imported from such a file returns zero, also known as
-`token.NoPos`.
-
+Objects for predeclared functions and types such as `len` and `int`
+do not have a valid (non-zero) position: `!obj.Pos().IsValid()`.
 
 
 There are eight kinds of objects in the Go type checker.
@@ -283,14 +273,22 @@ possible types, and we commonly use a type switch to distinguish them.
 	       | *Nil          // predeclared nil
 
 
-`Object`s are canonical.
-That is, two `Object`s `x` and `y` denote the same
-entity if and only if `x==y`.
+
+Objects are canonical.
+That is, two Objects `x` and `y` denote the same entity if and only if `x==y`.
+(This is generally true but beware that parameterized types complicate matters; see
+https://github.com/golang/exp/tree/master/typeparams/example for details.)
+
 Object identity is significant, and objects are routinely compared by
 the addresses of the underlying pointers.
-Although a package-level object is uniquely identified by its name
-and enclosing package, for other objects there is no simple way to
-obtain a string that uniquely identifies it.
+A package-level object (func/var/const/type) can be uniquely
+identified by its name and enclosing package.
+The [`golang.org/x/tools/go/types/objectpath`](https://pkg.go.dev/golang.org/x/tools/go/types/objectpath)
+package defines a naming scheme for objects that are
+[exported](#imports) from their package or are unexported but form part of the
+type of an exported object.
+But for most objects, including all function-local objects,
+there is no simple way to obtain a string that uniquely identifies it.
 
 
 
@@ -325,7 +323,7 @@ And some kinds of objects have methods in addition to those required by the
 
 
 `(*Func).Scope` returns the [lexical block](#scopes)
-containing the function's parameters, results,
+containing the function's type parameters, parameters, results,
 and other local declarations.
 `(*Var).IsField` distinguishes struct fields from ordinary
 variables, and `(*Var).Anonymous` discriminates named fields like
@@ -333,10 +331,12 @@ the one in `struct{T T}` from anonymous fields like the one in `struct{T}`.
 `(*Const).Val` returns the value of a named [constant](#constants).
 
 
-`(*TypeName).IsAlias`, introduced in Go 1.9, reports whether the
-type name is simply an alias for a type (as in `type I = int`),
-as opposed to a definition of a [`Named`](#named-types) type, as
-in `type Celsius float64`.
+`(*TypeName).IsAlias` reports whether the type name declares an alias
+for an existing type (as in `type I = int`), as opposed to defining a new
+[`Named`](#named-types) type, as in `type Celsius float64`.
+(Most `TypeName`s for which `IsAlias()` is true have a `Type()` of
+type `*types.Alias`, but `IsAlias()` is also true for the predeclared
+`byte` and `rune` types, which are aliases for `uint8` and `int32`.)
 
 
 `(*PkgName).Imported` returns the package (for instance,
@@ -352,9 +352,9 @@ We'll look more closely at this in [Imports](#imports).
 All relationships between the syntax trees (`ast.Node`s) and type
 checker data structures such as `Object`s and `Type`s are
 stored in mappings outside the syntax tree itself.
-Be aware that the `go/ast` package also defines a type called
-`Object` that resembles---and predates---the type checker's
-`Object`, and that `ast.Object`s are held directly by
+Be aware that the `go/ast` package also defines an older deprecated
+type called `Object` that resembles---and predates---the type
+checker's `Object`, and that `ast.Object`s are held directly by
 identifiers in the AST.
 They are created by the parser, which has a necessarily limited view
 of the package, so the information they represent is at best partial and
@@ -792,7 +792,7 @@ Here is the interface:
 	}
 
 
-And here are the eleven concrete types that satisfy it:
+And here are the 14 concrete types that satisfy it:
 
 
 	Type = *Basic
@@ -804,12 +804,17 @@ And here are the eleven concrete types that satisfy it:
 	     | *Struct
 	     | *Tuple
 	     | *Signature
+	     | *Alias
 	     | *Named
 	     | *Interface
+	     | *Union
+	     | *TypeParam
 
 
 With the exception of `Named` types, instances of `Type` are
 not canonical.
+(Even for `Named` types, parameterized types complicate matters; see
+https://github.com/golang/exp/tree/master/typeparams/example.)
 That is, it is usually a mistake to compare types using `t1==t2`
 since this equivalence is not the same as the
 [type identity relation](https://golang.org/ref/spec#Type_identity)
@@ -870,8 +875,8 @@ basic type this is.
 The kinds `Bool`, `String`, `Int16`, and so on,
 represent the corresponding predeclared boolean, string, or numeric
 types.
-There are two synonyms: `Byte` is equivalent to `Uint8`
-and `Rune` is equivalent to `Int32`.
+There are two aliases: `Byte` is an alias for `Uint8`
+and `Rune` is an alias for `Int32`.
 The kind `UnsafePointer` represents `unsafe.Pointer`.
 The kinds `UntypedBool`, `UntypedInt` and so on represent
 the six kinds of "untyped" constant types: boolean, integer, rune,
@@ -900,21 +905,20 @@ modify it.
 
 
 A few minor subtleties:
-According to the Go spec, pre-declared types such as `int` are
-named types for the purposes of assignability, even though the type
-checker does not represent them using `Named`.
-And `unsafe.Pointer` is a pointer type for the purpose of
-determining whether the receiver type of a method is legal, even
-though the type checker does not represent it using `Pointer`.
-
 
+- According to the Go spec, pre-declared types such as `int` are
+  named types for the purposes of assignability, even though the type
+  checker does not represent them using `Named`.
 
-The "untyped" types are usually only ascribed to constant expressions,
-but there is one exception.
-A comparison `x==y` has type "untyped bool", so the result of
-this expression may be assigned to a variable of type `bool` or
-any other named boolean type.
+- `unsafe.Pointer` is a pointer type for the purpose of
+  determining whether the receiver type of a method is legal, even
+  though the type checker does not represent it using `Pointer`.
 
+- The "untyped" types are usually only ascribed to constant expressions,
+  but there is one exception.
+  A comparison `x==y` has type "untyped bool", so the result of
+  this expression may be assigned to a variable of type `bool` or
+  any other named boolean type.
 
 
 ## Simple Composite Types
@@ -1080,21 +1084,73 @@ These types are recorded during type checking for later use
 
 
 
-## Named Types
+## Alias Types
+
+Type declarations come in two forms, aliases and defined types.
 
+Aliases, though introduced only in Go 1.9 and not very common, are
+simplest, so we'll present them first and explain defined types in
+the next section ("Named Types").
 
-Type declarations come in two forms.
-The simplest kind, introduced in Go 1.9,
-merely declares a (possibly alternative) name for an existing type.
-Type names used in this way are informally called _type aliases_.
-For example, this declaration lets you use the type
-`Dictionary` as an alias for `map[string]string`:
+An alias type declaration declares an alternative name for an existing
+type. For example, this declaration lets you use the type `Dictionary`
+as a synonym for `map[string]string`:
 
 	type Dictionary = map[string]string
 
-The declaration creates a `TypeName` object for `Dictionary`.  The
-object's `IsAlias` method returns true, and its `Type` method returns
-a `Map` type that represents `map[string]string`.
+The declaration creates a `TypeName` object for `Dictionary`.
+The object's `IsAlias` method returns true,
+and its `Type` method returns an `Alias`:
+
+        type Alias struct{ ... }
+	func (a *Alias) Obj() *TypeName
+	func (a *Alias) Origin() *Alias
+	func (a *Alias) Rhs() Type
+	func (a *Alias) SetTypeParams(tparams []*TypeParam)
+	func (a *Alias) TypeArgs() *TypeList
+	func (a *Alias) TypeParams() *TypeParamList
+
+The type on the right-hand side of an alias declaration,
+such as `map[string]string` in the example above,
+can be accessed using the `Rhs()` method.
+The `types.Unalias(t)` helper function recursively applies `Rhs`,
+removing all `Alias` types from the operand t and returning the
+outermost non-alias type.
+
+The `Obj` method returns the declaring `TypeName` object, such as
+`Dictionary`; it provides the name, position, and other properties of
+the declaration. Conversely, the `TypeName` object's `Type` method
+returns the `Alias` type.
+
+Starting with Go 1.24, alias types may have type parameters.
+For example, this declaration creates an Alias type with
+a type parameter:
+
+	type Set[T comparable] = map[T]bool
+
+Each instantiation such as `Set[string]` is identical to the
+corresponding instantiation of the alias' right-hand side type, such
+as `map[string]bool`.
+
+The remaining methods--Origin, SetTypeParams, TypeArgs,
+TypeParams--are all concerned with type parameters. For now, see
+https://github.com/golang/exp/tree/master/typeparams/example.
+
+Prior to Go 1.22, aliases were not materialized as `Alias` types:
+each reference to an alias type such as `Dictionary` would be
+immediately replaced by its right-hand side type, leaving no
+indication in the output of the type checker that an alias was
+present.
+By materializing alias types, optionally in Go 1.22 and by default
+starting in Go 1.23, we can more faithfully record the structure of
+the program, which improves the quality of diagnostic messages and
+enables certain analyses and code transformations. And, crucially, it
+enabled the addition of parameterized aliases in Go 1.24.)
+
+
+
+## Named Types
+
 
 
 The second form of type declaration, and the only kind prior to Go
@@ -1110,10 +1166,11 @@ from any other type, including `float64`.  The declaration binds the
 
 Since Go 1.9, the Go language specification has used the term _defined
 types_ instead of named types;
-the essential property of a defined type is not that it has a name,
+the essential property of a defined type is not that it has a name
+(aliases and type parameters also have names)
 but that it is a distinct type with its own method set.
 However, the type checker API predates that
-change and instead calls defined types "named" types.
+change and instead calls defined types `Named` types.
 
 	type Named struct{ ... }
 	func (*Named) NumMethods() int
@@ -1139,8 +1196,8 @@ methods than this list.  We'll return to this in [Method Sets](#method-sets).
 
 
 Every `Type` has an `Underlying` method, but for all of them
-except `*Named`, it is simply the identity function.
-For a named type, `Underlying` returns its underlying type, which
+except `*Named` and `*Alias`, it is simply the identity function.
+For a named or alias type, `Underlying` returns its underlying type, which
 is always an unnamed type.
 Thus `Underlying` returns `int` for both `T` and
 `U` below.
@@ -1153,14 +1210,13 @@ Thus `Underlying` returns `int` for both `T` and
 Clients of the type checker often use type assertions or type switches
 with a `Type` operand.
 When doing so, it is often necessary to switch on the type that
-_underlies_ the type of interest, and failure to do so may be a
-bug.
+underlies the type of interest, and failure to do so may be a bug.
 
 This is a common pattern:
 
 
 	// handle types of composite literal
-	switch u := t.Underlying().(type) {
+	switch u := t.Underlying().(type) {  // remove any *Named and *Alias types
 	case *Struct:        // ...
 	case *Map:           // ...
 	case *Array, *Slice: // ...
@@ -1241,6 +1297,36 @@ interface `v`, then the type assertion is not legal, as in this example:
 
 
 
+
+## TypeParam types
+
+
+A `TypeParam` is the type of a type parameter.
+For example, the type of the variable `x` in the `identity` function
+below is a `TypeParam`:
+
+    func identity[T any](x T) T { return x }
+
+As with `Alias` and `Named` types, each `TypeParam` has an associated
+`TypeName` object that provides its name, position, and other
+properties of the declaration.
+
+See https://github.com/golang/exp/tree/master/typeparams/example
+for a more thorough treatment of parameterized types.
+
+
+
+
+## Union types
+
+A `Union` is the type of type-parameter constraint of the form `func
+f[T int | string]`.
+
+See https://github.com/golang/exp/tree/master/typeparams/example
+for a more thorough treatment of parameterized types.
+
+
+
 ## TypeAndValue
 
 
@@ -1820,10 +1906,9 @@ compiler file formats, and so on.
 	}
 
 
-Most of our examples used the simplest `Importer` implementation,
+Most of our examples used the trivial `Importer` implementation,
 `importer.Default()`, provided by the `go/importer` package.
-This importer looks in `$GOROOT` and `$GOPATH` for `.a`
-files written by the compiler (`gc` or `gccgo`)
+This importer looks for `.a` files written by the compiler
 that was used to build the program.
 In addition to object code, these files contain _export data_,
 that is, a description of all the objects declared by the package, and
@@ -1835,13 +1920,11 @@ transitive dependency.
 
 
 Compiler export data is compact and efficient to locate, load, and
-parse, but it has several shortcomings.
-First, it does not contain position information for imported
-objects, reducing the quality of certain diagnostic messages.
-Second, it does not contain complete syntax trees nor semantic information
-about the contents of function bodies, so it is not suitable for
-interprocedural analyses.
-Third, compiler object data may be stale.  Nothing detects or ensures
+parse, but it has some shortcomings.
+First, it does not contain complete syntax trees nor semantic
+information about the bodies of all functions, so it is not
+suitable for interprocedural analyses.
+Second, compiler object data may be stale.  Nothing detects or ensures
 that the object files are more recent than the source files from which
 they were derived.
 Generally, object data for standard packages is likely to be

From 40afcb705d05179afce97d51b6677e46b5b48bf5 Mon Sep 17 00:00:00 2001
From: Jonathan Amsterdam 
Date: Sun, 15 Dec 2024 07:20:53 -0500
Subject: [PATCH 38/48] slog-handler-guide: fix typo, tweak phrasing

Fix one typo and improve the clarity and phrasing of some prose.

Change-Id: I4cec5b052df0d33f6e4a3f2751313b3c1d59ae2c
Reviewed-on: https://go-review.googlesource.com/c/example/+/636275
LUCI-TryBot-Result: Go LUCI 
Reviewed-by: Alan Donovan 
Reviewed-by: Ian Lance Taylor 
---
 slog-handler-guide/guide.md | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/slog-handler-guide/guide.md b/slog-handler-guide/guide.md
index 4472bcaa..b79b5e21 100644
--- a/slog-handler-guide/guide.md
+++ b/slog-handler-guide/guide.md
@@ -15,7 +15,7 @@ The standard library’s `log/slog` package has a two-part design.
 A "frontend," implemented by the `Logger` type,
 gathers structured log information like a message, level, and attributes,
 and passes them to a "backend," an implementation of the `Handler` interface.
-The package comes with two built-in handlers that usually should be adequate.
+The package comes with two built-in handlers that should usually be adequate.
 But you may need to write your own handler, and that is not always straightforward.
 This guide is here to help.
 
@@ -28,7 +28,7 @@ types work together.
 Each logger contains a handler. Certain `Logger` methods do some preliminary work,
 such as gathering key-value pairs into `Attr`s, and then call one or more
 `Handler` methods. These `Logger` methods are `With`, `WithGroup`,
-and the output methods.
+and the output methods like `Info`, `Error` and so on.
 
 An output method fulfills the main role of a logger: producing log output.
 Here is a call to an output method:
@@ -314,7 +314,7 @@ Most of the fields of `IndentHandler` can be copied shallowly, but the slice of
 the same underlying array. If we used `append` instead of making an explicit
 copy, we would introduce that subtle aliasing bug.
 
-Using `withGroupOrAttrs`, the `With` methods are easy:
+The `With` methods are easy to write using `withGroupOrAttrs`:
 
 %include indenthandler2/indent_handler.go withs -
 
@@ -362,7 +362,7 @@ See [this bug report](https://go.dev/issue/61321) for more detail.
 
 ### With pre-formatting
 
-Our second implementation implements pre-formatting.
+Our second version of the `WithGroup` and `WithAttrs` methods provides pre-formatting.
 This implementation is more complicated than the previous one.
 Is the extra complexity worth it?
 That depends on your circumstances, but here is one circumstance where
@@ -410,7 +410,7 @@ We also need to track how many groups we've opened, which we can do
 with a simple counter, since an opened group's only effect is to change the
 indentation level.
 
-The `WithGroup` implementation is a lot like the previous one: just remember the
+This `WithGroup` is a lot like the previous one: it just remembers the
 new group, which is unopened initially.
 
 %include indenthandler3/indent_handler.go WithGroup -
@@ -571,7 +571,7 @@ impossible to inspect a system, as is typically the case with a production
 server, logs provide the most detailed way to understand its behavior.
 Therefore, your handler should be robust to bad input.
 
-For example, the usual advice when when a function discovers a problem,
+For example, the usual advice when a function discovers a problem,
 like an invalid argument, is to panic or return an error.
 The built-in handlers do not follow that advice.
 Few things are more frustrating than being unable to debug a problem that

From 398e112173f492ddf8d48c8b76ea9965a21fa308 Mon Sep 17 00:00:00 2001
From: Jonathan Amsterdam 
Date: Wed, 26 Mar 2025 11:17:24 -0400
Subject: [PATCH 39/48] slog-handler-guide: warn against embedding

For golang/go#73057.

Change-Id: Id9ee1c0af03f9b49f6063aa946b6fe5cfbc02483
Reviewed-on: https://go-review.googlesource.com/c/example/+/660956
LUCI-TryBot-Result: Go LUCI 
Reviewed-by: Dmitri Shuralyov 
Reviewed-by: Dmitri Shuralyov 
---
 slog-handler-guide/guide.md | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/slog-handler-guide/guide.md b/slog-handler-guide/guide.md
index b79b5e21..4207e7a1 100644
--- a/slog-handler-guide/guide.md
+++ b/slog-handler-guide/guide.md
@@ -94,6 +94,11 @@ so it will sometimes produce invalid YAML.
 For example, it doesn't quote keys that have colons in them.
 We'll call it `IndentHandler` to forestall disappointment.
 
+A brief aside before we start: it is tempting to embed `slog.Handler` in your
+custom handler and implement only the methods that you need.
+Loggers and handlers are too tightly coupled for that to work. You should
+implement all four handler methods.
+
 We begin with the `IndentHandler` type
 and the `New` function that constructs it from an `io.Writer` and options:
 

From dd59d6852dfb14d1df8ea02959345bc72cdc5c03 Mon Sep 17 00:00:00 2001
From: Adam 
Date: Mon, 7 Apr 2025 15:30:11 +0000
Subject: [PATCH 40/48] slog-handler-guide: remove doubled word

Change-Id: I2fc1ead51b4da07a56201ceab4abd4f9e0643294
GitHub-Last-Rev: bca7c8e7ef1c904b29f6b133a9857bde7b646cc0
GitHub-Pull-Request: golang/example#38
Reviewed-on: https://go-review.googlesource.com/c/example/+/662795
LUCI-TryBot-Result: Go LUCI 
Reviewed-by: Dmitri Shuralyov 
Reviewed-by: Jonathan Amsterdam 
Auto-Submit: Dmitri Shuralyov 
Reviewed-by: Sean Liao 
---
 slog-handler-guide/README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/slog-handler-guide/README.md b/slog-handler-guide/README.md
index c8fd0d80..8ab958ad 100644
--- a/slog-handler-guide/README.md
+++ b/slog-handler-guide/README.md
@@ -853,7 +853,7 @@ impossible to inspect a system, as is typically the case with a production
 server, logs provide the most detailed way to understand its behavior.
 Therefore, your handler should be robust to bad input.
 
-For example, the usual advice when when a function discovers a problem,
+For example, the usual advice when a function discovers a problem,
 like an invalid argument, is to panic or return an error.
 The built-in handlers do not follow that advice.
 Few things are more frustrating than being unable to debug a problem that

From 24b880c977e2156eec5965ad1ffd066661e30ace Mon Sep 17 00:00:00 2001
From: Adam Bender 
Date: Tue, 8 Apr 2025 18:58:56 -0700
Subject: [PATCH 41/48] example/weave: update TOC anchors to remove **, _, and
 `

When GitHub's Markdown parser creates anchors for sections, it removes
formatting directives like **, _, and `. See the discussion at
https://support.github.com/ticket/personal/0/3324945

The links to the sections with formatting directives in the current TOC at https://github.com/golang/example/blob/master/slog-handler-guide/README.md
are broken for this reason.

This change modifies weave to also remove those characters from the
anchor links it generates.

This change also replaces the deprecated os.SET_SEEK with io.SeekStart
and removes two unused functions.

Change-Id: I66e3aa8140e14146bdb349c4ccfb773fe7414c67
Reviewed-on: https://go-review.googlesource.com/c/example/+/664015
LUCI-TryBot-Result: Go LUCI 
Reviewed-by: Jonathan Amsterdam 
Reviewed-by: Junyang Shao 
---
 internal/cmd/weave/weave.go | 20 ++++++++------------
 1 file changed, 8 insertions(+), 12 deletions(-)

diff --git a/internal/cmd/weave/weave.go b/internal/cmd/weave/weave.go
index 91c06e4c..d833653d 100644
--- a/internal/cmd/weave/weave.go
+++ b/internal/cmd/weave/weave.go
@@ -28,6 +28,7 @@ import (
 	"bufio"
 	"bytes"
 	"fmt"
+	"io"
 	"log"
 	"os"
 	"path/filepath"
@@ -72,11 +73,12 @@ func main() {
 			depth := len(words[0])
 			words = words[1:]
 			text := strings.Join(words, " ")
-			for i := range words {
-				words[i] = strings.ToLower(words[i])
-			}
-			line = fmt.Sprintf("%s1. [%s](#%s)",
-				strings.Repeat("\t", depth-1), text, strings.Join(words, "-"))
+			anchor := strings.Join(words, "-")
+			anchor = strings.ToLower(anchor)
+			anchor = strings.ReplaceAll(anchor, "**", "")
+			anchor = strings.ReplaceAll(anchor, "`", "")
+			anchor = strings.ReplaceAll(anchor, "_", "")
+			line = fmt.Sprintf("%s1. [%s](#%s)", strings.Repeat("\t", depth-1), text, anchor)
 			toc = append(toc, line)
 		}
 	}
@@ -85,7 +87,7 @@ func main() {
 	}
 
 	// Pass 2.
-	if _, err := f.Seek(0, os.SEEK_SET); err != nil {
+	if _, err := f.Seek(0, io.SeekStart); err != nil {
 		log.Fatalf("can't rewind input: %v", err)
 	}
 	in = bufio.NewScanner(f)
@@ -173,12 +175,6 @@ func include(file, tag string) (string, error) {
 	return text.String(), nil
 }
 
-func isBlank(line string) bool { return strings.TrimSpace(line) == "" }
-
-func indented(line string) bool {
-	return strings.HasPrefix(line, "    ") || strings.HasPrefix(line, "\t")
-}
-
 // cleanListing removes entirely blank leading and trailing lines from
 // text, and removes n leading tabs.
 func cleanListing(text string) string {

From f8bc3a00c46867f81091612ae6792ad64e1c7af9 Mon Sep 17 00:00:00 2001
From: Adam Bender 
Date: Thu, 24 Apr 2025 20:35:31 -0700
Subject: [PATCH 42/48] example/slog-handler-guide: run `make`

This change is the result of running `make` to produce README.md from
guide.md. It appears that the last time `make` was run was
2024-09-06 for 4e46ff5. I confirmed that all diffs to README.md
were deliberately introduced to guide.md (in 398e112 and 40afcb7).

In addition, one change to README.md (32022ca) was backported to
guide.md.

Change-Id: I6a27b8d128630f7f7e5799ad963fd96fb33ac3df
Reviewed-on: https://go-review.googlesource.com/c/example/+/668075
Reviewed-by: Junyang Shao 
Reviewed-by: Jonathan Amsterdam 
LUCI-TryBot-Result: Go LUCI 
Auto-Submit: Jonathan Amsterdam 
---
 slog-handler-guide/README.md | 25 +++++++++++++++----------
 slog-handler-guide/guide.md  |  2 +-
 2 files changed, 16 insertions(+), 11 deletions(-)

diff --git a/slog-handler-guide/README.md b/slog-handler-guide/README.md
index 8ab958ad..01027cc4 100644
--- a/slog-handler-guide/README.md
+++ b/slog-handler-guide/README.md
@@ -9,11 +9,11 @@ This document is maintained by Jonathan Amsterdam `jba@google.com`.
 
 1. [Introduction](#introduction)
 1. [Loggers and their handlers](#loggers-and-their-handlers)
-1. [Implementing `Handler` methods](#implementing-`handler`-methods)
-	1. [The `Enabled` method](#the-`enabled`-method)
-	1. [The `Handle` method](#the-`handle`-method)
-	1. [The `WithAttrs` method](#the-`withattrs`-method)
-	1. [The `WithGroup` method](#the-`withgroup`-method)
+1. [Implementing `Handler` methods](#implementing-handler-methods)
+	1. [The `Enabled` method](#the-enabled-method)
+	1. [The `Handle` method](#the-handle-method)
+	1. [The `WithAttrs` method](#the-withattrs-method)
+	1. [The `WithGroup` method](#the-withgroup-method)
 	1. [Testing](#testing)
 1. [General considerations](#general-considerations)
 	1. [Copying records](#copying-records)
@@ -28,7 +28,7 @@ The standard library’s `log/slog` package has a two-part design.
 A "frontend," implemented by the `Logger` type,
 gathers structured log information like a message, level, and attributes,
 and passes them to a "backend," an implementation of the `Handler` interface.
-The package comes with two built-in handlers that usually should be adequate.
+The package comes with two built-in handlers that should usually be adequate.
 But you may need to write your own handler, and that is not always straightforward.
 This guide is here to help.
 
@@ -41,7 +41,7 @@ types work together.
 Each logger contains a handler. Certain `Logger` methods do some preliminary work,
 such as gathering key-value pairs into `Attr`s, and then call one or more
 `Handler` methods. These `Logger` methods are `With`, `WithGroup`,
-and the output methods.
+and the output methods like `Info`, `Error` and so on.
 
 An output method fulfills the main role of a logger: producing log output.
 Here is a call to an output method:
@@ -107,6 +107,11 @@ so it will sometimes produce invalid YAML.
 For example, it doesn't quote keys that have colons in them.
 We'll call it `IndentHandler` to forestall disappointment.
 
+A brief aside before we start: it is tempting to embed `slog.Handler` in your
+custom handler and implement only the methods that you need.
+Loggers and handlers are too tightly coupled for that to work. You should
+implement all four handler methods.
+
 We begin with the `IndentHandler` type
 and the `New` function that constructs it from an `io.Writer` and options:
 
@@ -439,7 +444,7 @@ Most of the fields of `IndentHandler` can be copied shallowly, but the slice of
 the same underlying array. If we used `append` instead of making an explicit
 copy, we would introduce that subtle aliasing bug.
 
-Using `withGroupOrAttrs`, the `With` methods are easy:
+The `With` methods are easy to write using `withGroupOrAttrs`:
 
 ```
 func (h *IndentHandler) WithGroup(name string) slog.Handler {
@@ -543,7 +548,7 @@ See [this bug report](https://go.dev/issue/61321) for more detail.
 
 ### With pre-formatting
 
-Our second implementation implements pre-formatting.
+Our second version of the `WithGroup` and `WithAttrs` methods provides pre-formatting.
 This implementation is more complicated than the previous one.
 Is the extra complexity worth it?
 That depends on your circumstances, but here is one circumstance where
@@ -600,7 +605,7 @@ We also need to track how many groups we've opened, which we can do
 with a simple counter, since an opened group's only effect is to change the
 indentation level.
 
-The `WithGroup` implementation is a lot like the previous one: just remember the
+This `WithGroup` is a lot like the previous one: it just remembers the
 new group, which is unopened initially.
 
 ```
diff --git a/slog-handler-guide/guide.md b/slog-handler-guide/guide.md
index 4207e7a1..f79f4e06 100644
--- a/slog-handler-guide/guide.md
+++ b/slog-handler-guide/guide.md
@@ -54,7 +54,7 @@ original remains unchanged.
 All subsequent output from `logger` will include those attributes.
 A logger's `With` method calls its handler's `WithAttrs` method.
 
-The `WithGroup` method is used to avoid avoid key collisions in large programs
+The `WithGroup` method is used to avoid key collisions in large programs
 by establishing separate namespaces.
 This call creates a new `Logger` value with a group named "g":
 

From e8420e48cb0ab72c0dc1ac230329e313ed218851 Mon Sep 17 00:00:00 2001
From: Jonathan Amsterdam 
Date: Sun, 18 May 2025 07:02:16 -0400
Subject: [PATCH 43/48] weave: fix %include parsing, add highlighting

Recognize that

  %include file -

means "no tag, no caption" instead of "the tag is -, add caption."

Write "go" after backticks, which GitHub will render with syntax
highlighting.

Change-Id: I9e0b3618b5fba8834d7f06b8fc7adf0781574a2d
Reviewed-on: https://go-review.googlesource.com/c/example/+/673875
Reviewed-by: Alan Donovan 
LUCI-TryBot-Result: Go LUCI 
---
 gotypes/README.md            | 50 ++++++++++++++++++------------------
 internal/cmd/weave/weave.go  | 36 ++++++++++++++++++--------
 slog-handler-guide/README.md | 32 +++++++++++------------
 3 files changed, 66 insertions(+), 52 deletions(-)

diff --git a/gotypes/README.md b/gotypes/README.md
index 350fbc68..9886a111 100644
--- a/gotypes/README.md
+++ b/gotypes/README.md
@@ -143,7 +143,7 @@ run `go get golang.org/x/example/gotypes/...`.
 
 	// go get golang.org/x/example/gotypes/pkginfo
 
-```
+```go
 package main
 
 import (
@@ -247,7 +247,7 @@ Finally, the program prints the attributes of the package, shown below.
 (The hexadecimal number may vary from one run to the next.)
 
 
-```
+```go
 $ go build golang.org/x/example/gotypes/pkginfo
 $ ./pkginfo
 Package  "cmd/hello"
@@ -505,7 +505,7 @@ identifier in the input program, and the object it refers to.
 
 	// go get golang.org/x/example/gotypes/defsuses
 
-```
+```go
 func PrintDefsUses(fset *token.FileSet, files ...*ast.File) error {
 	conf := types.Config{Importer: importer.Default()}
 	info := &types.Info{
@@ -535,7 +535,7 @@ Let's use the _hello, world_ program again as the input:
 
 	// go get golang.org/x/example/gotypes/hello
 
-```
+```go
 package main
 
 import "fmt"
@@ -549,7 +549,7 @@ func main() {
 This is what it prints:
 
 
-```
+```go
 $ go build golang.org/x/example/gotypes/defsuses
 $ ./defsuses
 hello.go:1:9: "main" defines 
@@ -796,7 +796,7 @@ preserve comments in the input.
 
 	// go get golang.org/x/example/gotypes/lookup
 
-```
+```go
 func main() {
 	fset := token.NewFileSet()
 	f, err := parser.ParseFile(fset, "hello.go", hello, parser.ParseComments)
@@ -839,7 +839,7 @@ The second comment looks up `"fmt"` in the `main` function's block,
 and so on.
 
 
-```
+```go
 const hello = `
 package main
 
@@ -863,7 +863,7 @@ func main() {
 Here's the output:
 
 
-```
+```go
 $ go build golang.org/x/example/gotypes/lookup
 $ ./lookup
 At hello.go:6:1,        "append" = builtin append
@@ -1566,7 +1566,7 @@ type-checked file and prints its type, value, and mode:
 
 	// go get golang.org/x/example/gotypes/typeandvalue
 
-```
+```go
 // f is a parsed, type-checked *ast.File.
 ast.Inspect(f, func(n ast.Node) bool {
 	if expr, ok := n.(ast.Expr); ok {
@@ -1596,7 +1596,7 @@ It makes use of these two helper functions, which are not shown:
 Given this input:
 
 
-```
+```go
 const input = `
 package main
 
@@ -1613,7 +1613,7 @@ func main() {
 the program prints:
 
 
-```
+```go
 $ go build golang.org/x/example/gotypes/typeandvalue
 $ ./typeandvalue
 make(map[string]int)            mode:  value
@@ -1672,7 +1672,7 @@ comparing a method `x.f` against nil is a common mistake.
 
 	// go get golang.org/x/example/gotypes/nilfunc
 
-```
+```go
 // CheckNilFuncComparison reports unintended comparisons
 // of functions against nil, e.g., "if x.Method == nil {".
 func CheckNilFuncComparison(info *types.Info, n ast.Node) {
@@ -1718,7 +1718,7 @@ func CheckNilFuncComparison(info *types.Info, n ast.Node) {
 Given this input,
 
 
-```
+```go
 const input = `package main
 
 import "bytes"
@@ -1736,7 +1736,7 @@ func main() {
 the program reports these errors:
 
 
-```
+```go
 $ go build golang.org/x/example/gotypes/nilfunc
 $ ./nilfunc
 input.go:7:5: comparison of function Bytes == nil is always false
@@ -1969,7 +1969,7 @@ interface.
 Here's an example:
 
 
-```
+```go
 $ ./skeleton io ReadWriteCloser buffer
 // *buffer implements io.ReadWriteCloser.
 type buffer struct{}
@@ -1993,7 +1993,7 @@ calls `PrintSkeleton` with the remaining two arguments:
 
 	// go get golang.org/x/example/gotypes/skeleton
 
-```
+```go
 func PrintSkeleton(pkg *types.Package, ifacename, concname string) error {
 	obj := pkg.Scope().Lookup(ifacename)
 	if obj == nil {
@@ -2051,7 +2051,7 @@ Passing `(*types.Package).Name` causes only the package name
 Here's another example that illustrates it:
 
 
-```
+```go
 $ ./skeleton net/http Handler myHandler
 // *myHandler implements net/http.Handler.
 type myHandler struct{}
@@ -2067,7 +2067,7 @@ in `pkg`, and reports the types that satisfy each interface type.
 
 	// go get golang.org/x/example/gotypes/implements
 
-```
+```go
 // Find all named types at package level.
 var allNamed []*types.Named
 for _, name := range pkg.Scope().Names() {
@@ -2099,7 +2099,7 @@ Given this input,
 
 	// go get golang.org/x/example/gotypes/implements
 
-```
+```go
 const input = `package main
 
 type A struct{}
@@ -2118,7 +2118,7 @@ type J interface { g() }
 the program prints:
 
 
-```
+```go
 $ go build golang.org/x/example/gotypes/implements
 $ ./implements
 *hello.A satisfies hello.I
@@ -2276,7 +2276,7 @@ programs.
 
 	// go get golang.org/x/example/gotypes/hugeparam
 
-```
+```go
 var bytesFlag = flag.Int("bytes", 48, "maximum parameter size in bytes")
 
 func PrintHugeParams(fset *token.FileSet, info *types.Info, sizes types.Sizes, files []*ast.File) {
@@ -2324,7 +2324,7 @@ It reports a number of places where the 7-word
 is copied.
 
 
-```
+```go
 % ./hugeparam encoding/xml
 /go/src/encoding/xml/marshal.go:167:50: "start" parameter: encoding/xml.StartElement = 56 bytes
 /go/src/encoding/xml/marshal.go:734:97: "" result: encoding/xml.StartElement = 56 bytes
@@ -2408,7 +2408,7 @@ the command line.
 Here's an example:
 
 
-```
+```go
 $ ./doc net/http File
 type net/http.File interface{Readdir(count int) ([]os.FileInfo, error); Seek(offset int64, whence int) (int64, error); Stat() (os.FileInfo, error); io.Closer; io.Reader}
 $GOROOT/src/io/io.go:92:2: method (net/http.File) Close() error
@@ -2435,7 +2435,7 @@ plus exported type information for its dependencies.
 
 	// go get golang.org/x/example/gotypes/doc
 
-```
+```go
 pkgpath, name := os.Args[1], os.Args[2]
 
 // Load complete type information for the specified packages,
@@ -2465,7 +2465,7 @@ The rest of the program prints the output:
 
 	// go get golang.org/x/example/gotypes/doc
 
-```
+```go
 // Print the object and its methods (incl. location of definition).
 fmt.Println(obj)
 for _, sel := range typeutil.IntuitiveMethodSet(obj.Type(), nil) {
diff --git a/internal/cmd/weave/weave.go b/internal/cmd/weave/weave.go
index d833653d..880b415c 100644
--- a/internal/cmd/weave/weave.go
+++ b/internal/cmd/weave/weave.go
@@ -27,6 +27,7 @@ package main
 import (
 	"bufio"
 	"bytes"
+	"flag"
 	"fmt"
 	"io"
 	"log"
@@ -37,13 +38,14 @@ import (
 )
 
 func main() {
+	flag.Parse()
 	log.SetFlags(0)
 	log.SetPrefix("weave: ")
-	if len(os.Args) != 2 {
+	if flag.NArg() != 1 {
 		log.Fatal("usage: weave input.md\n")
 	}
 
-	f, err := os.Open(os.Args[1])
+	f, err := os.Open(flag.Arg(0))
 	if err != nil {
 		log.Fatal(err)
 	}
@@ -100,26 +102,38 @@ func main() {
 			}
 		case strings.HasPrefix(line, "%include"):
 			words := strings.Fields(line)
-			if len(words) < 2 {
-				log.Fatal(line)
+			var section string
+			caption := true
+			switch len(words) {
+			case 2: // %include filename
+			// Nothing to do.
+			case 3: // %include filename section OR %include filename -
+				if words[2] == "-" {
+					caption = false
+				} else {
+					section = words[2]
+				}
+			case 4: // %include filename section -
+				section = words[2]
+				if words[3] != "-" {
+					log.Fatalf("last word is not '-': %s", line)
+				}
+				caption = false
+			default:
+				log.Fatalf("wrong # words (want 2-4): %s", line)
 			}
 			filename := words[1]
 
-			// Show caption unless '-' follows.
-			if len(words) < 4 || words[3] != "-" {
+			if caption {
 				fmt.Printf("	// go get golang.org/x/example/%s/%s\n\n",
 					curDir, filepath.Dir(filename))
 			}
 
-			section := ""
-			if len(words) > 2 {
-				section = words[2]
-			}
 			s, err := include(filename, section)
 			if err != nil {
 				log.Fatal(err)
 			}
-			fmt.Println("```")
+			fmt.Println("```go")
 			fmt.Println(cleanListing(s)) // TODO(adonovan): escape /^```/ in s
 			fmt.Println("```")
 		default:
diff --git a/slog-handler-guide/README.md b/slog-handler-guide/README.md
index 01027cc4..5342c066 100644
--- a/slog-handler-guide/README.md
+++ b/slog-handler-guide/README.md
@@ -115,7 +115,7 @@ implement all four handler methods.
 We begin with the `IndentHandler` type
 and the `New` function that constructs it from an `io.Writer` and options:
 
-```
+```go
 type IndentHandler struct {
 	opts Options
 	// TODO: state for WithGroup and WithAttrs
@@ -186,7 +186,7 @@ of the work done by each request to be controlled independently.
 Our `IndentHandler` doesn't use the context. It just compares the argument level
 with its configured minimum level:
 
-```
+```go
 func (h *IndentHandler) Enabled(ctx context.Context, level slog.Level) bool {
 	return level >= h.opts.Level.Level()
 }
@@ -232,7 +232,7 @@ groups established by `WithGroup`.
 
 That is how `IndentHandler.Handle` is structured:
 
-```
+```go
 func (h *IndentHandler) Handle(ctx context.Context, r slog.Record) error {
 	buf := make([]byte, 0, 1024)
 	if !r.Time.IsZero() {
@@ -287,7 +287,7 @@ goroutines that may be calling `Handle` at the same time.
 At the heart of the handler is the `appendAttr` method, responsible for
 formatting a single attribute:
 
-```
+```go
 func (h *IndentHandler) appendAttr(buf []byte, a slog.Attr, indentLevel int) []byte {
 	// Resolve the Attr's value before doing anything else.
 	a.Value = a.Value.Resolve()
@@ -405,7 +405,7 @@ Our first implementation will collect the information from `WithGroup` and
 and loop over that slice in `Handle`. We start with a struct that can hold
 either a group name or some attributes:
 
-```
+```go
 // groupOrAttrs holds either a group name or a list of slog.Attrs.
 type groupOrAttrs struct {
 	group string      // group name if non-empty
@@ -415,7 +415,7 @@ type groupOrAttrs struct {
 
 Then we add a slice of `groupOrAttrs` to our handler:
 
-```
+```go
 type IndentHandler struct {
 	opts Options
 	goas []groupOrAttrs
@@ -429,7 +429,7 @@ receiver.
 To that end, we define a method that will copy our handler struct
 and append one `groupOrAttrs` to the copy:
 
-```
+```go
 func (h *IndentHandler) withGroupOrAttrs(goa groupOrAttrs) *IndentHandler {
 	h2 := *h
 	h2.goas = make([]groupOrAttrs, len(h.goas)+1)
@@ -446,7 +446,7 @@ copy, we would introduce that subtle aliasing bug.
 
 The `With` methods are easy to write using `withGroupOrAttrs`:
 
-```
+```go
 func (h *IndentHandler) WithGroup(name string) slog.Handler {
 	if name == "" {
 		return h
@@ -465,7 +465,7 @@ func (h *IndentHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
 The `Handle` method can now process the groupOrAttrs slice after
 the built-in attributes and before the ones in the record:
 
-```
+```go
 func (h *IndentHandler) Handle(ctx context.Context, r slog.Record) error {
 	buf := make([]byte, 0, 1024)
 	if !r.Time.IsZero() {
@@ -587,7 +587,7 @@ by formatting the attributes in a call to `With` just once.
 To pre-format the arguments to `WithAttrs`, we need to keep track of some
 additional state in the `IndentHandler` struct.
 
-```
+```go
 type IndentHandler struct {
 	opts           Options
 	preformatted   []byte   // data from WithGroup and WithAttrs
@@ -608,7 +608,7 @@ indentation level.
 This `WithGroup` is a lot like the previous one: it just remembers the
 new group, which is unopened initially.
 
-```
+```go
 func (h *IndentHandler) WithGroup(name string) slog.Handler {
 	if name == "" {
 		return h
@@ -624,7 +624,7 @@ func (h *IndentHandler) WithGroup(name string) slog.Handler {
 
 `WithAttrs` does all the pre-formatting:
 
-```
+```go
 func (h *IndentHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
 	if len(attrs) == 0 {
 		return h
@@ -668,7 +668,7 @@ method we saw above.
 It's the `Handle` method's job to insert the pre-formatted material in the right
 place, which is after the built-in attributes and before the ones in the record:
 
-```
+```go
 func (h *IndentHandler) Handle(ctx context.Context, r slog.Record) error {
 	buf := make([]byte, 0, 1024)
 	if !r.Time.IsZero() {
@@ -728,7 +728,7 @@ That package's `TestHandler` function takes an instance of your handler and
 a function that returns its output formatted as a slice of maps. Here is the test function
 for our example handler:
 
-```
+```go
 func TestSlogtest(t *testing.T) {
 	var buf bytes.Buffer
 	err := slogtest.TestHandler(New(&buf, nil), func() []map[string]any {
@@ -754,7 +754,7 @@ of the box.
 Our example output is enough like YAML so that we can use the `gopkg.in/yaml.v3`
 package to parse it:
 
-```
+```go
 func parseLogEntries(t *testing.T, data []byte) []map[string]any {
 	entries := bytes.Split(data, []byte("---\n"))
 	entries = entries[:len(entries)-1] // last one is empty
@@ -985,7 +985,7 @@ re-assign it before freeing:
 
 Here is our pool and its associated functions:
 
-```
+```go
 var bufPool = sync.Pool{
 	New: func() any {
 		b := make([]byte, 0, 1024)

From 8b405629c4a5215871be932097e099c05ec5cb2e Mon Sep 17 00:00:00 2001
From: Dmitri Shuralyov 
Date: Thu, 5 Jun 2025 11:34:04 -0400
Subject: [PATCH 44/48] go.mod: update golang.org/x dependencies

Updating to a newer x/tools version fixes a build error at Go tip:

$ go version
go version go1.25-devel_eebae283b6 Mon Jun 2 15:28:33 2025 -0700 darwin/arm64  # CL 675736
$ go test -c -o=/dev/null ./...
# golang.org/x/tools/internal/tokeninternal
$GOMODCACHE/golang.org/x/tools@v0.14.0/internal/tokeninternal/tokeninternal.go:78:9: invalid array length -delta * delta (constant -256 of type int64)
FAIL	golang.org/x/example/gotypes/doc [build failed]

For golang/go#73205.

Change-Id: If5dc7113d3b51d36021a050aca2b329c9e46c1fd
Reviewed-on: https://go-review.googlesource.com/c/example/+/679037
LUCI-TryBot-Result: Go LUCI 
Auto-Submit: Dmitri Shuralyov 
Reviewed-by: Carlos Amedee 
Reviewed-by: Dmitri Shuralyov 
---
 go.mod | 12 +++++++-----
 go.sum | 15 ++++++++-------
 2 files changed, 15 insertions(+), 12 deletions(-)

diff --git a/go.mod b/go.mod
index e876fb92..51af5e4e 100644
--- a/go.mod
+++ b/go.mod
@@ -1,11 +1,13 @@
 module golang.org/x/example
 
-go 1.18
-
-require golang.org/x/tools v0.14.0
+go 1.23.0
 
 require (
-	golang.org/x/mod v0.13.0 // indirect
-	golang.org/x/sys v0.13.0 // indirect
+	golang.org/x/tools v0.33.0
 	gopkg.in/yaml.v3 v3.0.1
 )
+
+require (
+	golang.org/x/mod v0.25.0 // indirect
+	golang.org/x/sync v0.15.0 // indirect
+)
diff --git a/go.sum b/go.sum
index 25e95bda..8b33454f 100644
--- a/go.sum
+++ b/go.sum
@@ -1,10 +1,11 @@
-golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
-golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
-golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
-golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
-golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
-golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
+golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
+golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
+golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
+golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

From 7a4eec550fb2aaa1904ddcbc4eeaa9f4d995791f Mon Sep 17 00:00:00 2001
From: Rob Findley 
Date: Wed, 10 Sep 2025 18:34:46 +0000
Subject: [PATCH 45/48] internal/cmd/weave: add -o flag

Add a -o flag to the weave command, so that it can be more easily used
with go:generate.

Change-Id: I620c99528ab8156dd89700b1576c445c6ac7c55a
Reviewed-on: https://go-review.googlesource.com/c/example/+/702377
LUCI-TryBot-Result: Go LUCI 
Reviewed-by: Alan Donovan 
Auto-Submit: Robert Findley 
---
 internal/cmd/weave/weave.go | 77 +++++++++++++++++++++++++------------
 1 file changed, 52 insertions(+), 25 deletions(-)

diff --git a/internal/cmd/weave/weave.go b/internal/cmd/weave/weave.go
index 880b415c..d20e03cf 100644
--- a/internal/cmd/weave/weave.go
+++ b/internal/cmd/weave/weave.go
@@ -37,33 +37,60 @@ import (
 	"strings"
 )
 
+var output = flag.String("o", "", "output file (empty means stdout)")
+
 func main() {
+	flag.Usage = func() {
+		fmt.Fprintf(flag.CommandLine.Output(), "usage: weave [flags] \n\nflags:\n")
+		flag.PrintDefaults()
+	}
 	flag.Parse()
-	log.SetFlags(0)
-	log.SetPrefix("weave: ")
 	if flag.NArg() != 1 {
-		log.Fatal("usage: weave input.md\n")
+		flag.Usage()
+		os.Exit(2)
 	}
 
-	f, err := os.Open(flag.Arg(0))
+	log.SetFlags(0)
+	log.SetPrefix("weave: ")
+
+	wd, err := os.Getwd()
 	if err != nil {
 		log.Fatal(err)
 	}
-	defer f.Close()
+	curDir := filepath.Base(wd)
 
-	wd, err := os.Getwd()
+	in, err := os.Open(flag.Arg(0))
 	if err != nil {
 		log.Fatal(err)
 	}
-	curDir := filepath.Base(wd)
+	defer in.Close()
 
-	fmt.Println("")
+	out := os.Stdout
+	if *output != "" {
+		out, err = os.Create(*output)
+		if err != nil {
+			log.Fatal(err)
+		}
+		defer func() {
+			if err := out.Close(); err != nil {
+				log.Fatal(err)
+			}
+		}()
+	}
+
+	printf := func(format string, args ...any) {
+		if _, err := fmt.Fprintf(out, format, args...); err != nil {
+			log.Fatalf("writing failed: %v", err)
+		}
+	}
+
+	printf("")
 
 	// Pass 1: extract table of contents.
 	var toc []string
-	in := bufio.NewScanner(f)
-	for in.Scan() {
-		line := in.Text()
+	scanner := bufio.NewScanner(in)
+	for scanner.Scan() {
+		line := scanner.Text()
 		if line == "" || (line[0] != '#' && line[0] != '%') {
 			continue
 		}
@@ -84,21 +111,21 @@ func main() {
 			toc = append(toc, line)
 		}
 	}
-	if in.Err() != nil {
-		log.Fatal(in.Err())
+	if scanner.Err() != nil {
+		log.Fatal(scanner.Err())
 	}
 
 	// Pass 2.
-	if _, err := f.Seek(0, io.SeekStart); err != nil {
+	if _, err := in.Seek(0, io.SeekStart); err != nil {
 		log.Fatalf("can't rewind input: %v", err)
 	}
-	in = bufio.NewScanner(f)
-	for in.Scan() {
-		line := in.Text()
+	scanner = bufio.NewScanner(in)
+	for scanner.Scan() {
+		line := scanner.Text()
 		switch {
 		case strings.HasPrefix(line, "%toc"): // ToC
 			for _, h := range toc {
-				fmt.Println(h)
+				printf("%s\n", h)
 			}
 		case strings.HasPrefix(line, "%include"):
 			words := strings.Fields(line)
@@ -125,7 +152,7 @@ func main() {
 			filename := words[1]
 
 			if caption {
-				fmt.Printf("	// go get golang.org/x/example/%s/%s\n\n",
+				printf("	// go get golang.org/x/example/%s/%s\n\n",
 					curDir, filepath.Dir(filename))
 			}
 
@@ -133,15 +160,15 @@ func main() {
 			if err != nil {
 				log.Fatal(err)
 			}
-			fmt.Println("```go")
-			fmt.Println(cleanListing(s)) // TODO(adonovan): escape /^```/ in s
-			fmt.Println("```")
+			printf("```go\n")
+			printf("%s\n", cleanListing(s)) // TODO(adonovan): escape /^```/ in s
+			printf("```\n")
 		default:
-			fmt.Println(line)
+			printf("%s\n", line)
 		}
 	}
-	if in.Err() != nil {
-		log.Fatal(in.Err())
+	if scanner.Err() != nil {
+		log.Fatal(scanner.Err())
 	}
 }
 

From ca271338190ded05fc544764d6e84289cedf1354 Mon Sep 17 00:00:00 2001
From: Rob Findley 
Date: Wed, 10 Sep 2025 20:45:46 +0000
Subject: [PATCH 46/48] internal/cmd/weave: fix missing newline

Change-Id: Icd6026b2eafae254c0637a806e771334b4232fbd
Reviewed-on: https://go-review.googlesource.com/c/example/+/702378
Auto-Submit: Robert Findley 
LUCI-TryBot-Result: Go LUCI 
Reviewed-by: Alan Donovan 
---
 internal/cmd/weave/weave.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/internal/cmd/weave/weave.go b/internal/cmd/weave/weave.go
index d20e03cf..bca5c82a 100644
--- a/internal/cmd/weave/weave.go
+++ b/internal/cmd/weave/weave.go
@@ -84,7 +84,7 @@ func main() {
 		}
 	}
 
-	printf("")
+	printf("\n")
 
 	// Pass 1: extract table of contents.
 	var toc []string

From 53021d5c81c3add612aadcc6ffa2cc33a65d07f7 Mon Sep 17 00:00:00 2001
From: Rob Findley 
Date: Mon, 15 Sep 2025 18:40:51 +0000
Subject: [PATCH 47/48] internal/cmd/weave: keep track of minimum header depth

Allow for outlines that start at a header level greater than 1, by
keeping track of the minimum header level encountered in the table of
contents.

Change-Id: I779726531883ecae3f980eb47cefe886ffef8b4f
Reviewed-on: https://go-review.googlesource.com/c/example/+/703895
Reviewed-by: Alan Donovan 
Auto-Submit: Robert Findley 
LUCI-TryBot-Result: Go LUCI 
---
 internal/cmd/weave/weave.go | 23 +++++++++++++++++++----
 1 file changed, 19 insertions(+), 4 deletions(-)

diff --git a/internal/cmd/weave/weave.go b/internal/cmd/weave/weave.go
index bca5c82a..20d16ea8 100644
--- a/internal/cmd/weave/weave.go
+++ b/internal/cmd/weave/weave.go
@@ -87,7 +87,19 @@ func main() {
 	printf("\n")
 
 	// Pass 1: extract table of contents.
-	var toc []string
+	type tocEntry struct {
+		depth  int
+		text   string
+		anchor string
+	}
+	var (
+		toc []tocEntry
+		// We indent toc items according to their header depth, so that nested
+		// headers result in nested lists. However, we want the lowest header depth
+		// to correspond to the root of the list. Otherwise, the entire list is
+		// indented, which turns it into a code block rather than an outline.
+		minTocDepth int
+	)
 	scanner := bufio.NewScanner(in)
 	for scanner.Scan() {
 		line := scanner.Text()
@@ -97,9 +109,13 @@ func main() {
 		line = strings.TrimSpace(line)
 		if line == "%toc" {
 			toc = nil
+			minTocDepth = 0
 		} else if strings.HasPrefix(line, "# ") || strings.HasPrefix(line, "## ") {
 			words := strings.Fields(line)
 			depth := len(words[0])
+			if minTocDepth == 0 || depth < minTocDepth {
+				minTocDepth = depth
+			}
 			words = words[1:]
 			text := strings.Join(words, " ")
 			anchor := strings.Join(words, "-")
@@ -107,8 +123,7 @@ func main() {
 			anchor = strings.ReplaceAll(anchor, "**", "")
 			anchor = strings.ReplaceAll(anchor, "`", "")
 			anchor = strings.ReplaceAll(anchor, "_", "")
-			line = fmt.Sprintf("%s1. [%s](#%s)", strings.Repeat("\t", depth-1), text, anchor)
-			toc = append(toc, line)
+			toc = append(toc, tocEntry{depth: depth, text: text, anchor: anchor})
 		}
 	}
 	if scanner.Err() != nil {
@@ -125,7 +140,7 @@ func main() {
 		switch {
 		case strings.HasPrefix(line, "%toc"): // ToC
 			for _, h := range toc {
-				printf("%s\n", h)
+				printf("%s1. [%s](#%s)\n", strings.Repeat("\t", h.depth-minTocDepth), h.text, h.anchor)
 			}
 		case strings.HasPrefix(line, "%include"):
 			words := strings.Fields(line)

From 7f05d217867b2af52b0a28c6d1c91df97e1b5b39 Mon Sep 17 00:00:00 2001
From: Rob Findley 
Date: Mon, 15 Sep 2025 19:56:04 +0000
Subject: [PATCH 48/48] internal/cmd/weave: print two levels of headings in TOC

Print up to two levels of headings, independent of the root depth of the
table of contents.

Change-Id: I75c07a5983b2af2639ebb9bf76e4189a7b114143
Reviewed-on: https://go-review.googlesource.com/c/example/+/703975
Auto-Submit: Robert Findley 
Reviewed-by: Alan Donovan 
LUCI-TryBot-Result: Go LUCI 
---
 internal/cmd/weave/weave.go | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)

diff --git a/internal/cmd/weave/weave.go b/internal/cmd/weave/weave.go
index 20d16ea8..ce983cc8 100644
--- a/internal/cmd/weave/weave.go
+++ b/internal/cmd/weave/weave.go
@@ -9,7 +9,7 @@
 // exceptions:
 //
 // If a line begins with "%toc", it is replaced with a table of contents
-// consisting of links to the top two levels of headers ("#" and "##").
+// consisting of links to the top two levels of headers below the %toc symbol.
 //
 // If a line begins with "%include FILENAME TAG", it is replaced with the lines
 // of the file between lines containing "!+TAG" and  "!-TAG". TAG can be omitted,
@@ -110,7 +110,11 @@ func main() {
 		if line == "%toc" {
 			toc = nil
 			minTocDepth = 0
-		} else if strings.HasPrefix(line, "# ") || strings.HasPrefix(line, "## ") {
+		} else if strings.HasPrefix(line, "# ") ||
+			strings.HasPrefix(line, "## ") ||
+			strings.HasPrefix(line, "### ") ||
+			strings.HasPrefix(line, "#### ") {
+
 			words := strings.Fields(line)
 			depth := len(words[0])
 			if minTocDepth == 0 || depth < minTocDepth {
@@ -140,7 +144,10 @@ func main() {
 		switch {
 		case strings.HasPrefix(line, "%toc"): // ToC
 			for _, h := range toc {
-				printf("%s1. [%s](#%s)\n", strings.Repeat("\t", h.depth-minTocDepth), h.text, h.anchor)
+				// Only print two levels of headings.
+				if h.depth-minTocDepth <= 1 {
+					printf("%s1. [%s](#%s)\n", strings.Repeat("\t", h.depth-minTocDepth), h.text, h.anchor)
+				}
 			}
 		case strings.HasPrefix(line, "%include"):
 			words := strings.Fields(line)