Skip to content

Commit

Permalink
fix: write logs to stderr instead of stdout, fixes #753 (#759)
Browse files Browse the repository at this point in the history
  • Loading branch information
a-h authored May 26, 2024
1 parent 0d42d67 commit 19ffb1c
Show file tree
Hide file tree
Showing 16 changed files with 210 additions and 192 deletions.
2 changes: 1 addition & 1 deletion .version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.2.707
0.2.708
49 changes: 17 additions & 32 deletions cmd/templ/fmtcmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,45 +11,26 @@ import (
"time"

"github.com/a-h/templ/cmd/templ/processor"
"github.com/a-h/templ/cmd/templ/sloghandler"
parser "github.com/a-h/templ/parser/v2"
"github.com/natefinch/atomic"
)

type Arguments struct {
ToStdout bool
Files []string
LogLevel string
WorkerCount int
}

func Run(w io.Writer, args Arguments) (err error) {
func Run(log *slog.Logger, stdin io.Reader, stdout io.Writer, args Arguments) (err error) {
// If no files are provided, read from stdin and write to stdout.
if len(args.Files) == 0 {
return format(writeToStdout, readFromStdin)
}

level := slog.LevelInfo.Level()
switch args.LogLevel {
case "debug":
level = slog.LevelDebug.Level()
case "warn":
level = slog.LevelWarn.Level()
case "error":
level = slog.LevelError.Level()
}
log := slog.New(sloghandler.NewHandler(w, &slog.HandlerOptions{
AddSource: args.LogLevel == "debug",
Level: level,
}))
if args.ToStdout {
log = slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{}))
return format(writeToWriter(stdout), readFromReader(stdin))
}
process := func(fileName string) error {
read := readFromFile(fileName)
write := writeToFile
if args.ToStdout {
write = writeToStdout
write = writeToWriter(stdout)
}
return format(write, read)
}
Expand Down Expand Up @@ -101,12 +82,14 @@ func (f *Formatter) Run() (err error) {

type reader func() (fileName, src string, err error)

func readFromStdin() (fileName, src string, err error) {
b, err := io.ReadAll(os.Stdin)
if err != nil {
return "", "", fmt.Errorf("failed to read stdin: %w", err)
func readFromReader(r io.Reader) func() (fileName, src string, err error) {
return func() (fileName, src string, err error) {
b, err := io.ReadAll(r)
if err != nil {
return "", "", fmt.Errorf("failed to read stdin: %w", err)
}
return "stdin.templ", string(b), nil
}
return "stdin.templ", string(b), nil
}

func readFromFile(name string) reader {
Expand All @@ -123,11 +106,13 @@ type writer func(fileName, tgt string) error

var mu sync.Mutex

func writeToStdout(fileName, tgt string) error {
mu.Lock()
defer mu.Unlock()
_, err := os.Stdout.Write([]byte(tgt))
return err
func writeToWriter(w io.Writer) func(fileName, tgt string) error {
return func(fileName, tgt string) error {
mu.Lock()
defer mu.Unlock()
_, err := w.Write([]byte(tgt))
return err
}
}

func writeToFile(fileName, tgt string) error {
Expand Down
16 changes: 8 additions & 8 deletions cmd/templ/generatecmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"errors"
"fmt"
"io"
"log/slog"
"net/http"
"net/url"
Expand Down Expand Up @@ -55,9 +54,14 @@ func (cmd Generate) Run(ctx context.Context) (err error) {
if cmd.Args.Watch && cmd.Args.FileName != "" {
return fmt.Errorf("cannot watch a single file, remove the -f or -watch flag")
}
if cmd.Args.FileName == "" && cmd.Args.ToStdout {
writingToWriter := cmd.Args.FileWriter != nil
if cmd.Args.FileName == "" && writingToWriter {
return fmt.Errorf("only a single file can be output to stdout, add the -f flag to specify the file to generate code for")
}
// Default to writing to files.
if cmd.Args.FileWriter == nil {
cmd.Args.FileWriter = FileWriter
}
if cmd.Args.PPROFPort > 0 {
go func() {
_ = http.ListenAndServe(fmt.Sprintf("localhost:%d", cmd.Args.PPROFPort), nil)
Expand All @@ -81,10 +85,6 @@ func (cmd Generate) Run(ctx context.Context) (err error) {
opts = append(opts, generator.WithTimestamp(time.Now()))
}

if cmd.Args.ToStdout {
cmd.Log = slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{}))
}

// Check the version of the templ module.
if err := modcheck.Check(cmd.Args.Path); err != nil {
cmd.Log.Warn("templ version check: " + err.Error())
Expand All @@ -97,7 +97,7 @@ func (cmd Generate) Run(ctx context.Context) (err error) {
opts,
cmd.Args.GenerateSourceMapVisualisations,
cmd.Args.KeepOrphanedFiles,
cmd.Args.ToStdout,
cmd.Args.FileWriter,
)

// If we're processing a single file, don't bother setting up the channels/multithreaing.
Expand Down Expand Up @@ -182,7 +182,7 @@ func (cmd Generate) Run(ctx context.Context) (err error) {
opts,
cmd.Args.GenerateSourceMapVisualisations,
cmd.Args.KeepOrphanedFiles,
cmd.Args.ToStdout,
cmd.Args.FileWriter,
)
errorCount.Store(0)
if err := watcher.WalkFiles(ctx, cmd.Args.Path, events); err != nil {
Expand Down
36 changes: 19 additions & 17 deletions cmd/templ/generatecmd/eventhandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"go/format"
"go/scanner"
"go/token"
"io"
"log/slog"
"os"
"path"
Expand All @@ -23,14 +24,27 @@ import (
"github.com/fsnotify/fsnotify"
)

type FileWriterFunc func(name string, contents []byte) error

func FileWriter(fileName string, contents []byte) error {
return os.WriteFile(fileName, contents, 0o644)
}

func WriterFileWriter(w io.Writer) FileWriterFunc {
return func(_ string, contents []byte) error {
_, err := w.Write(contents)
return err
}
}

func NewFSEventHandler(
log *slog.Logger,
dir string,
devMode bool,
genOpts []generator.GenerateOpt,
genSourceMapVis bool,
keepOrphanedFiles bool,
toStdout bool,
fileWriter FileWriterFunc,
) *FSEventHandler {
if !path.IsAbs(dir) {
dir, _ = filepath.Abs(dir)
Expand All @@ -48,10 +62,7 @@ func NewFSEventHandler(
genSourceMapVis: genSourceMapVis,
DevMode: devMode,
keepOrphanedFiles: keepOrphanedFiles,
writer: writeToFile,
}
if toStdout {
fseh.writer = writeToStdout
writer: fileWriter,
}
if devMode {
fseh.genOpts = append(fseh.genOpts, generator.WithExtractStrings())
Expand All @@ -77,15 +88,6 @@ type FSEventHandler struct {
writer func(string, []byte) error
}

func writeToFile(fileName string, contents []byte) error {
return os.WriteFile(fileName, contents, 0o644)
}

func writeToStdout(_ string, contents []byte) error {
_, err := os.Stdout.Write(contents)
return err
}

func (h *FSEventHandler) HandleEvent(ctx context.Context, event fsnotify.Event) (goUpdated, textUpdated bool, err error) {
// Handle _templ.go files.
if !event.Has(fsnotify.Remove) && strings.HasSuffix(event.Name, "_templ.go") {
Expand Down Expand Up @@ -223,8 +225,8 @@ func (h *FSEventHandler) generate(ctx context.Context, fileName string) (goUpdat

formattedGoCode, err := format.Source(b.Bytes())
if err != nil {
err = remapErrorList(err, sourceMap, fileName, targetFileName)
return false, false, nil, fmt.Errorf("%s source formatting error %w", fileName, err)
err = remapErrorList(err, sourceMap, fileName)
return false, false, nil, fmt.Errorf("% source formatting error %w", fileName, err)
}

// Hash output, and write out the file if the goCodeHash has changed.
Expand Down Expand Up @@ -262,7 +264,7 @@ func (h *FSEventHandler) generate(ctx context.Context, fileName string) (goUpdat

// Takes an error from the formatter and attempts to convert the positions reported in the target file to their positions
// in the source file.
func remapErrorList(err error, sourceMap *parser.SourceMap, fileName string, targetFileName string) error {
func remapErrorList(err error, sourceMap *parser.SourceMap, fileName string) error {
list, ok := err.(scanner.ErrorList)
if !ok || len(list) == 0 {
return err
Expand Down
21 changes: 2 additions & 19 deletions cmd/templ/generatecmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,14 @@ package generatecmd
import (
"context"
_ "embed"
"io"
"log/slog"

_ "net/http/pprof"

"github.com/a-h/templ/cmd/templ/sloghandler"
)

type Arguments struct {
FileName string
ToStdout bool
FileWriter FileWriterFunc
Path string
Watch bool
OpenBrowser bool
Expand All @@ -26,25 +23,11 @@ type Arguments struct {
GenerateSourceMapVisualisations bool
IncludeVersion bool
IncludeTimestamp bool
LogLevel string
// PPROFPort is the port to run the pprof server on.
PPROFPort int
KeepOrphanedFiles bool
}

func Run(ctx context.Context, w io.Writer, args Arguments) (err error) {
level := slog.LevelInfo.Level()
switch args.LogLevel {
case "debug":
level = slog.LevelDebug.Level()
case "warn":
level = slog.LevelWarn.Level()
case "error":
level = slog.LevelError.Level()
}
log := slog.New(sloghandler.NewHandler(w, &slog.HandlerOptions{
AddSource: args.LogLevel == "debug",
Level: level,
}))
func Run(ctx context.Context, log *slog.Logger, args Arguments) (err error) {
return NewGenerate(log, args).Run(ctx)
}
3 changes: 2 additions & 1 deletion cmd/templ/generatecmd/test-eventhandler/eventhandler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ func TestErrorLocationMapping(t *testing.T) {
}

slog := slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{}))
fseh := generatecmd.NewFSEventHandler(slog, ".", false, []generator.GenerateOpt{}, false, false, true)
var fw generatecmd.FileWriterFunc
fseh := generatecmd.NewFSEventHandler(slog, ".", false, []generator.GenerateOpt{}, false, false, fw)
for _, test := range tests {
// The raw files cannot end in .templ because they will cause the generator to fail. Instead,
// we create a tmp file that ends in .templ only for the duration of the test.
Expand Down
33 changes: 22 additions & 11 deletions cmd/templ/generatecmd/testwatch/generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"context"
"embed"
"fmt"
"io"
"log/slog"
"net"
"net/http"
"os"
Expand Down Expand Up @@ -368,16 +370,23 @@ func Setup(gzipEncoding bool) (args TestArgs, teardown func(t *testing.T), err e
command += " -gzip true"
}

cmdErr = generatecmd.Run(ctx, os.Stdout, generatecmd.Arguments{
Path: appDir,
Watch: true,
Command: command,
ProxyBind: proxyBind,
ProxyPort: proxyPort,
Proxy: args.AppURL,
IncludeVersion: false,
IncludeTimestamp: false,
KeepOrphanedFiles: false,
log := slog.New(slog.NewJSONHandler(io.Discard, nil))

cmdErr = generatecmd.Run(ctx, log, generatecmd.Arguments{
Path: appDir,
Watch: true,
OpenBrowser: false,
Command: command,
ProxyBind: proxyBind,
ProxyPort: proxyPort,
Proxy: args.AppURL,
NotifyProxy: false,
WorkerCount: 0,
GenerateSourceMapVisualisations: false,
IncludeVersion: false,
IncludeTimestamp: false,
PPROFPort: 0,
KeepOrphanedFiles: false,
})
}()

Expand Down Expand Up @@ -460,8 +469,10 @@ func TestGenerateReturnsErrors(t *testing.T) {
t.Errorf("failed to replace text in file: %v", err)
}

log := slog.New(slog.NewJSONHandler(io.Discard, nil))

// Run.
err = generatecmd.Run(context.Background(), os.Stdout, generatecmd.Arguments{
err = generatecmd.Run(context.Background(), log, generatecmd.Arguments{
Path: appDir,
Watch: false,
IncludeVersion: false,
Expand Down
6 changes: 3 additions & 3 deletions cmd/templ/lspcmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ type Arguments struct {
HTTPDebug string
}

func Run(w io.Writer, args Arguments) (err error) {
func Run(stdin io.Reader, stdout, stderr io.Writer, args Arguments) (err error) {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
signalChan := make(chan os.Signal, 1)
Expand Down Expand Up @@ -61,14 +61,14 @@ func Run(w io.Writer, args Arguments) (err error) {
}
log, err = cfg.Build()
if err != nil {
_, _ = fmt.Fprintf(w, "failed to create logger: %v\n", err)
_, _ = fmt.Fprintf(stderr, "failed to create logger: %v\n", err)
os.Exit(1)
}
}
defer func() {
_ = log.Sync()
}()
templStream := jsonrpc2.NewStream(newStdRwc(log, "templStream", w, os.Stdin))
templStream := jsonrpc2.NewStream(newStdRwc(log, "templStream", stdout, stdin))
return run(ctx, log, templStream, args)
}

Expand Down
Loading

0 comments on commit 19ffb1c

Please sign in to comment.