log: update logging option to include file output
This commit is contained in:
parent
c0db85e85f
commit
528e408f4d
3
go.mod
3
go.mod
|
@ -26,6 +26,7 @@ require (
|
|||
github.com/mattn/go-sqlite3 v1.14.17
|
||||
github.com/ogen-go/ogen v1.2.2
|
||||
github.com/rs/cors v1.11.0
|
||||
github.com/samber/slog-multi v1.2.0
|
||||
github.com/simukti/sqldb-logger v0.0.0-20230108155151-646c1a075551
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
|
@ -63,6 +64,7 @@ require (
|
|||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494 // indirect
|
||||
github.com/samber/lo v1.38.1 // indirect
|
||||
github.com/segmentio/asm v1.2.0 // indirect
|
||||
github.com/stephenafamo/scan v0.4.2 // indirect
|
||||
github.com/stoewer/go-strcase v1.3.0 // indirect
|
||||
|
@ -77,6 +79,7 @@ require (
|
|||
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
|
||||
google.golang.org/grpc v1.64.0 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
6
go.sum
6
go.sum
|
@ -147,6 +147,10 @@ github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99
|
|||
github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po=
|
||||
github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
|
||||
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||
github.com/samber/slog-multi v1.2.0 h1:JIebVdmeGkCMd5/ticlmU+aDYl4tdAZBmp5uLaSzr0k=
|
||||
github.com/samber/slog-multi v1.2.0/go.mod h1:uLAvHpGqbYgX4FSL0p1ZwoLuveIAJvBECtE07XmYvFo=
|
||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||
|
@ -284,6 +288,8 @@ google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWn
|
|||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/yaml.v2 v2.2.2/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=
|
||||
|
|
|
@ -16,7 +16,7 @@ full_bin = ""
|
|||
include_dir = []
|
||||
include_ext = ["go", "tpl", "tmpl", "html"]
|
||||
include_file = []
|
||||
kill_delay = "0s"
|
||||
kill_delay = "5s"
|
||||
log = "build-errors.log"
|
||||
poll = false
|
||||
poll_interval = 0
|
||||
|
|
1
go/.gitignore
vendored
1
go/.gitignore
vendored
|
@ -1,3 +1,4 @@
|
|||
models/
|
||||
*.db
|
||||
gen/
|
||||
.env
|
||||
|
|
|
@ -31,9 +31,20 @@ var Cmd = &cobra.Command{
|
|||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
cfg := config.FromContext(ctx)
|
||||
logOutput := log.WrapOsFile(os.Stderr)
|
||||
prettyHandler := log.NewPrettyHandler(logOutput, nil)
|
||||
slog.SetDefault(slog.New(prettyHandler))
|
||||
var cleanups []func() error
|
||||
defer func() {
|
||||
var e []error
|
||||
for _, c := range cleanups {
|
||||
e = append(e, c())
|
||||
}
|
||||
if err := errors.Join(e...); err != nil {
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
logHandler, cleanup := log.NewHandler(cfg)
|
||||
cleanups = append(cleanups, cleanup)
|
||||
slog.SetDefault(slog.New(logHandler))
|
||||
|
||||
if err := os.MkdirAll(cfg.String("download.directory"), 0755); err != nil {
|
||||
return errs.Wrapw(err, "failed to create download directory", "path", cfg.String("download.directory"))
|
||||
|
@ -46,6 +57,7 @@ var Cmd = &cobra.Command{
|
|||
"dsn", cfg.String("db.dsn"),
|
||||
)
|
||||
}
|
||||
cleanups = append(cleanups, sqldb.Close)
|
||||
|
||||
sqldb = sqldblogger.OpenDriver(
|
||||
"file:data.db",
|
||||
|
@ -98,7 +110,7 @@ var Cmd = &cobra.Command{
|
|||
return errs.Wrap(err, "failed to serve")
|
||||
}
|
||||
slog.Info("ConnectRPC server stopped")
|
||||
return errors.Join(sqldb.Close(), prettyHandler.Flush())
|
||||
return nil
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
|
|
@ -26,10 +26,11 @@ func (e Entries) ToMap() map[string]any {
|
|||
}
|
||||
|
||||
var DefaultConfig = Entries{
|
||||
{"log.enable", true, `Enable console logging. If TTY is detected, a pretty logging will be used. Otherwise uses JSON format.`, false},
|
||||
{"log.file.path", path.Join(xdg.CacheHome, "bluemage", "bluemage.log"), "Log file path", false},
|
||||
{"log.file.enable", true, "Enable file logging", false},
|
||||
{"log.level", "info", `Log level. Possible values: "debug", "info", "warn", "error"`, false},
|
||||
{"log.output", "stderr", "Log output. Possible values: \"stdout\", \"stderr\"", false},
|
||||
{"log.file.path", path.Join(xdg.CacheHome, "bluemage", "bluemage.log"), "Log file path", false},
|
||||
|
||||
{"db.driver", "sqlite3", "Database driver", false},
|
||||
{"db.path", path.Join(xdg.Home, ".local", "share", "bluemage", "data.db"), "Database path", false},
|
||||
|
@ -64,6 +65,7 @@ var DefaultConfig = Entries{
|
|||
{"telemetry.openobserve.trace.auth", "Basic AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "Authorization token for the Endpoint URL", false},
|
||||
{"telemetry.trace.ratio", float64(1), "Sampling ratio between 0 to 1 on how many traces are sent to the server. Value of 1 will send everything", false},
|
||||
|
||||
{"telemetry.service.name", "bluemage", "Name of the service to send to telemetry server", true},
|
||||
{"runtime.version", Version, "current server version", true},
|
||||
{"runtime.environment", "development", "runtime environment", true},
|
||||
{"flags.containerized", false, "Indicates the application is running in a container", true},
|
||||
|
|
108
go/pkg/log/log.go
Normal file
108
go/pkg/log/log.go
Normal file
|
@ -0,0 +1,108 @@
|
|||
package log
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/mattn/go-colorable"
|
||||
"github.com/mattn/go-isatty"
|
||||
slogmulti "github.com/samber/slog-multi"
|
||||
"github.com/tigorlazuardi/bluemage/go/config"
|
||||
"github.com/tigorlazuardi/bluemage/go/pkg/telemetry"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
||||
func NewHandler(cfg *config.Config) (slog.Handler, func() error) {
|
||||
var handlers []slog.Handler
|
||||
cleanup := func() error { return nil }
|
||||
|
||||
if cfg.Bool("log.enable") {
|
||||
log, sync := createStandardLogger(cfg)
|
||||
cleanup = sync
|
||||
handlers = append(handlers, log)
|
||||
}
|
||||
|
||||
if cfg.Bool("log.file.enable") {
|
||||
log, clean := createFileLogger(cfg)
|
||||
cl := cleanup
|
||||
cleanup = func() error {
|
||||
return errors.Join(cl(), clean())
|
||||
}
|
||||
handlers = append(handlers, log)
|
||||
}
|
||||
|
||||
if cfg.Bool("telemetry.openobserve.enable") && cfg.Bool("telemetry.openobserve.log.enable") {
|
||||
handlers = append(handlers, createO2Logger(cfg))
|
||||
}
|
||||
|
||||
if len(handlers) == 0 {
|
||||
return NullHandler{}, cleanup
|
||||
}
|
||||
|
||||
return slogmulti.Fanout(handlers...), cleanup
|
||||
}
|
||||
|
||||
func createFileLogger(cfg *config.Config) (slog.Handler, func() error) {
|
||||
output := &lumberjack.Logger{
|
||||
Filename: cfg.String("log.file.path"),
|
||||
MaxSize: 15,
|
||||
MaxAge: 30,
|
||||
MaxBackups: 10,
|
||||
LocalTime: true,
|
||||
}
|
||||
var lvl slog.Level
|
||||
_ = lvl.UnmarshalText(cfg.Bytes("log.level"))
|
||||
opts := &slog.HandlerOptions{
|
||||
AddSource: cfg.Bool("log.source"),
|
||||
Level: lvl,
|
||||
}
|
||||
return slog.NewJSONHandler(Lock(AddSync(output)), opts), output.Close
|
||||
}
|
||||
|
||||
func createStandardLogger(cfg *config.Config) (slog.Handler, func() error) {
|
||||
var output WriteSyncer
|
||||
var cleanup func() error
|
||||
|
||||
if strings.ToLower(cfg.String("log.output")) == "stdout" {
|
||||
output = Lock(AddSync(colorable.NewColorableStdout()))
|
||||
cleanup = output.Sync
|
||||
} else {
|
||||
output = Lock(AddSync(colorable.NewColorableStderr()))
|
||||
cleanup = output.Sync
|
||||
}
|
||||
|
||||
var lvl slog.Level
|
||||
_ = lvl.UnmarshalText(cfg.Bytes("log.level"))
|
||||
opts := &slog.HandlerOptions{
|
||||
AddSource: cfg.Bool("log.source"),
|
||||
Level: lvl,
|
||||
}
|
||||
|
||||
if isatty.IsTerminal(os.Stdout.Fd()) {
|
||||
return NewPrettyHandler(output, opts), cleanup
|
||||
} else {
|
||||
return slog.NewJSONHandler(output, opts), cleanup
|
||||
}
|
||||
}
|
||||
|
||||
func createO2Logger(cfg *config.Config) slog.Handler {
|
||||
var lvl slog.Level
|
||||
_ = lvl.UnmarshalText(cfg.Bytes("telemetry.openobserve.log.level"))
|
||||
opts := &slog.HandlerOptions{
|
||||
AddSource: cfg.Bool("telemetry.openobserve.log.source"),
|
||||
Level: lvl,
|
||||
}
|
||||
return telemetry.NewOpenObserveHandler(telemetry.OpenObserveHandlerOptions{
|
||||
HandlerOptions: opts,
|
||||
BufferSize: cfg.Int("telemetry.openobserve.log.buffer.size"),
|
||||
BufferTimeout: cfg.Duration("telemetry.openobserve.log.buffer.timeout"),
|
||||
Concurrency: cfg.Int("telemetry.openobserve.log.concurrency"),
|
||||
Endpoint: cfg.String("telemetry.openobserve.log.endpoint"),
|
||||
HTTPClient: http.DefaultClient,
|
||||
Username: cfg.String("telemetry.openobserve.log.username"),
|
||||
Password: cfg.String("telemetry.openobserve.log.password"),
|
||||
})
|
||||
}
|
13
go/pkg/log/null.go
Normal file
13
go/pkg/log/null.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package log
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
type NullHandler struct{}
|
||||
|
||||
func (NullHandler) Enabled(context.Context, slog.Level) bool { return false }
|
||||
func (NullHandler) Handle(context.Context, slog.Record) error { return nil }
|
||||
func (nu NullHandler) WithAttrs([]slog.Attr) slog.Handler { return nu }
|
||||
func (nu NullHandler) WithGroup(string) slog.Handler { return nu }
|
|
@ -1,8 +1,8 @@
|
|||
package telemetry
|
||||
|
||||
import (
|
||||
"go.opencensus.io/trace"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
// EndWithStatus ends the span with the status of the error if not nil
|
||||
|
|
|
@ -54,12 +54,7 @@ func createProvider(ctx context.Context, cfg *config.Config) (*sdktrace.TracerPr
|
|||
opts = append(opts, sdktrace.WithBatcher(o2exporter))
|
||||
}
|
||||
|
||||
res := resource.NewWithAttributes(
|
||||
semconv.SchemaURL,
|
||||
semconv.ServiceNameKey.String("bluemage"),
|
||||
semconv.ServiceVersionKey.String(cfg.String("runtime.version")),
|
||||
attribute.String("environment", cfg.String("runtime.environment")),
|
||||
)
|
||||
res := NewResource(cfg)
|
||||
|
||||
opts = append(opts, sdktrace.WithResource(res))
|
||||
|
||||
|
@ -72,3 +67,16 @@ func (te *Telemetry) Close() error {
|
|||
|
||||
return te.tracer.Shutdown(ctx)
|
||||
}
|
||||
|
||||
func NewResource(cfg *config.Config) *resource.Resource {
|
||||
res, err := resource.Merge(resource.Default(), resource.NewWithAttributes(
|
||||
semconv.SchemaURL,
|
||||
semconv.ServiceNameKey.String("bluemage"),
|
||||
semconv.ServiceVersionKey.String(cfg.String("runtime.version")),
|
||||
attribute.String("environment", cfg.String("runtime.environment")),
|
||||
))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue