diff --git a/go.mod b/go.mod index 376ef20..9091cfb 100644 --- a/go.mod +++ b/go.mod @@ -7,14 +7,36 @@ toolchain go1.24.3 require ( github.com/goxkit/configs v0.0.0-20250508205347-5d3cd3f9e2ae github.com/stretchr/testify v1.10.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 + go.opentelemetry.io/otel/log v0.12.2 + go.opentelemetry.io/otel/sdk/log v0.12.2 go.uber.org/zap v1.27.0 ) require ( + github.com/cenkalti/backoff/v5 v5.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/objx v0.5.2 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/bridges/otelzap v0.11.0 // indirect + go.opentelemetry.io/otel v1.36.0 // indirect + go.opentelemetry.io/otel/metric v1.36.0 // indirect + go.opentelemetry.io/otel/sdk v1.36.0 // indirect + go.opentelemetry.io/otel/trace v1.36.0 // indirect + go.opentelemetry.io/proto/otlp v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.25.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect + google.golang.org/grpc v1.72.1 // indirect + google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index c5d8bc0..7d48874 100644 --- a/go.sum +++ b/go.sum @@ -1,20 +1,80 @@ +github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= +github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= 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/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/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +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/goxkit/configs v0.0.0-20250508205347-5d3cd3f9e2ae h1:F1FfkahmdIHyczEx0i0DNlY7rIl7tITfyO8At0gDRAI= github.com/goxkit/configs v0.0.0-20250508205347-5d3cd3f9e2ae/go.mod h1:ZkpBs3TirjXTcJ8XkA03/LJ7rwiUjD8r1Czo3OiFEf4= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/bridges/otelzap v0.11.0 h1:u2E32P7j1a/gRgZDWhIXC+Shd4rLg70mnE7QLI/Ssnw= +go.opentelemetry.io/contrib/bridges/otelzap v0.11.0/go.mod h1:pJPCLM8gzX4ASqLlyAXjHBEYxgbOQJ/9bidWxD6PEPQ= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 h1:06ZeJRe5BnYXceSM9Vya83XXVaNGe3H1QqsvqRANQq8= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2/go.mod h1:DvPtKE63knkDVP88qpatBj81JxN+w1bqfVbsbCbj1WY= +go.opentelemetry.io/otel/log v0.12.2 h1:yob9JVHn2ZY24byZeaXpTVoPS6l+UrrxmxmPKohXTwc= +go.opentelemetry.io/otel/log v0.12.2/go.mod h1:ShIItIxSYxufUMt+1H5a2wbckGli3/iCfuEbVZi/98E= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/log v0.12.2 h1:yNoETvTByVKi7wHvYS6HMcZrN5hFLD7I++1xIZ/k6W0= +go.opentelemetry.io/otel/sdk/log v0.12.2/go.mod h1:DcpdmUXHJgSqN/dh+XMWa7Vf89u9ap0/AAk/XGLnEzY= +go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc h1:uqxdywfHqqCl6LmZzI3pUnXT1RGFYyUgxj0AkWPFxi0= +go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc/go.mod h1:TY/N/FT7dmFrP/r5ym3g0yysP1DefqGpAZr4f82P0dE= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI= +go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0= +google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= +google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/log/.gitkeep b/log/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/logging.go b/logging.go index ee4270e..65c5773 100644 --- a/logging.go +++ b/logging.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023, The GoKit Authors +// Copyright (c) 2025, The GoKit Authors // MIT License // All rights reserved. @@ -7,11 +7,11 @@ package logging import ( - "os" - "github.com/goxkit/configs" "go.uber.org/zap" "go.uber.org/zap/zapcore" + + "github.com/goxkit/logging/otlp" ) type ( @@ -39,98 +39,16 @@ type ( } ) -var ( - // openFile is a variable that holds the os.OpenFile function, - // allowing it to be replaced in tests. - openFile = os.OpenFile -) - // NewDefaultLogger creates a new logger that outputs to stdout. // It configures the logger based on the environment: // - Production/Staging: Uses JSON encoder // - Development: Uses colored console output // // The log level is determined by the configuration provided. -func NewDefaultLogger(cfgs *configs.Configs) (Logger, error) { - zapLogLevel := mapZapLogLevel(cfgs.AppConfigs) - - if cfgs.AppConfigs.GoEnv == configs.ProductionEnv || cfgs.AppConfigs.GoEnv == configs.StagingEnv { - logConfig := zap.NewProductionEncoderConfig() - logConfig.EncodeTime = zapcore.ISO8601TimeEncoder - encoder := zapcore.NewJSONEncoder(logConfig) - - cfgs.Logger = zap.New(zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), zapLogLevel)).Named(cfgs.AppConfigs.AppName) - - return cfgs.Logger, nil - } - - logConfig := zap.NewDevelopmentEncoderConfig() - logConfig.EncodeTime = zapcore.ISO8601TimeEncoder - logConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder - consoleEncoder := zapcore.NewConsoleEncoder(logConfig) - - cfgs.Logger = zap.New(zapcore.NewCore(consoleEncoder, zapcore.AddSync(os.Stdout), zapLogLevel)).Named(cfgs.AppConfigs.AppName) - - return cfgs.Logger, nil -} - -// NewFileLogger creates a logger that outputs to both a file and stdout. -// The file path is specified in the configuration. -// In production/staging environments, it only outputs to the file in JSON format. -// In development environments, it outputs to both stdout (colored) and the file (JSON). -func NewFileLogger(cfgs *configs.Configs) (Logger, error) { - zapLogLevel := mapZapLogLevel(cfgs.AppConfigs) - - file, err := openFile( - cfgs.AppConfigs.LogPath, - os.O_APPEND|os.O_CREATE|os.O_WRONLY, - 0644, - ) - if err != nil { - return nil, err - } - - if cfgs.AppConfigs.GoEnv == configs.ProductionEnv || cfgs.AppConfigs.GoEnv == configs.StagingEnv { - logConfig := zap.NewProductionEncoderConfig() - logConfig.EncodeTime = zapcore.ISO8601TimeEncoder - encoder := zapcore.NewJSONEncoder(logConfig) - - cfgs.Logger = zap.New(zapcore.NewCore(encoder, zapcore.AddSync(file), zapLogLevel)).Named(cfgs.AppConfigs.AppName) - - return cfgs.Logger, nil +func NewLogger(cfgs *configs.Configs) (Logger, error) { + if cfgs.OTLPConfigs.Enabled { + return otlp.Install(cfgs) } - logConfig := zap.NewDevelopmentEncoderConfig() - logConfig.EncodeTime = zapcore.ISO8601TimeEncoder - logConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder - consoleEncoder := zapcore.NewConsoleEncoder(logConfig) - fileEncoder := zapcore.NewJSONEncoder(logConfig) - - core := zapcore.NewTee( - zapcore.NewCore(consoleEncoder, zapcore.AddSync(os.Stdout), zapLogLevel), - zapcore.NewCore(fileEncoder, zapcore.AddSync(file), zapLogLevel), - ) - - cfgs.Logger = zap.New(core).Named(cfgs.AppConfigs.AppName) - - return cfgs.Logger, nil -} - -// mapZapLogLevel converts the application config log level to the corresponding -// Zap log level. It defaults to InfoLevel if the level is not recognized. -func mapZapLogLevel(e *configs.AppConfigs) zapcore.Level { - switch e.LogLevel { - case configs.DEBUG: - return zap.DebugLevel - case configs.INFO: - return zap.InfoLevel - case configs.WARN: - return zap.WarnLevel - case configs.ERROR: - return zap.ErrorLevel - case configs.PANIC: - return zap.PanicLevel - default: - return zap.InfoLevel - } + return nil, nil } diff --git a/logging_test.go b/logging_test.go deleted file mode 100644 index 7c7db17..0000000 --- a/logging_test.go +++ /dev/null @@ -1,110 +0,0 @@ -package logging - -import ( - "errors" - "fmt" - "os" - "testing" - - "github.com/goxkit/configs" - "github.com/stretchr/testify/suite" - "go.uber.org/zap" -) - -type LoggerTestSuite struct { - suite.Suite -} - -func TestLoggerTestSuite(t *testing.T) { - suite.Run(t, new(LoggerTestSuite)) -} - -func (s *LoggerTestSuite) SetupTest() { - openFile = os.OpenFile -} - -func (s *LoggerTestSuite) TestMapZapLogLevel() { - s.Equal(mapZapLogLevel(&configs.AppConfigs{LogLevel: configs.DEBUG}), zap.DebugLevel) - s.Equal(mapZapLogLevel(&configs.AppConfigs{LogLevel: configs.INFO}), zap.InfoLevel) - s.Equal(mapZapLogLevel(&configs.AppConfigs{LogLevel: configs.WARN}), zap.WarnLevel) - s.Equal(mapZapLogLevel(&configs.AppConfigs{LogLevel: configs.ERROR}), zap.ErrorLevel) - s.Equal(mapZapLogLevel(&configs.AppConfigs{LogLevel: configs.PANIC}), zap.PanicLevel) -} - -func (s *LoggerTestSuite) TestNewDefaultLoggerProd() { - logConfigs := configs.Configs{ - AppConfigs: &configs.AppConfigs{ - GoEnv: configs.ProductionEnv, - LogLevel: configs.DEBUG, - }, - } - - logger, err := NewDefaultLogger(&logConfigs) - - s.NoError(err) - s.IsType(&zap.Logger{}, logger) -} - -func (s *LoggerTestSuite) TestNewDefaultLoggerDev() { - logConfigs := configs.Configs{ - AppConfigs: &configs.AppConfigs{ - GoEnv: configs.DevelopmentEnv, - LogLevel: configs.DEBUG, - }, - } - - logger, err := NewDefaultLogger(&logConfigs) - - s.NoError(err) - s.IsType(&zap.Logger{}, logger) -} - -func (s *LoggerTestSuite) TestNewFileLoggerProd() { - logConfigs := configs.Configs{ - AppConfigs: &configs.AppConfigs{ - GoEnv: configs.ProductionEnv, - LogLevel: configs.DEBUG, - LogPath: "./log/file.log", - }, - } - - fmt.Println(os.Getwd()) - - logger, err := NewFileLogger(&logConfigs) - - s.NoError(err) - s.IsType(&zap.Logger{}, logger) -} - -func (s *LoggerTestSuite) TestNewFileLoggerDev() { - logConfigs := configs.Configs{ - AppConfigs: &configs.AppConfigs{ - GoEnv: configs.DevelopmentEnv, - LogLevel: configs.DEBUG, - LogPath: "./log/file.log", - }, - } - - logger, err := NewFileLogger(&logConfigs) - - s.NoError(err) - s.IsType(&zap.Logger{}, logger) -} - -func (s *LoggerTestSuite) TestNewFileLoggerErrInOpenFile() { - openFile = func(_ string, flag int, perm os.FileMode) (*os.File, error) { - return nil, errors.New("some error") - } - - logConfigs := configs.Configs{ - AppConfigs: &configs.AppConfigs{ - GoEnv: configs.DevelopmentEnv, - LogLevel: configs.DEBUG, - LogPath: "./log/file.log", - }, - } - - _, err := NewFileLogger(&logConfigs) - - s.Error(err) -} diff --git a/mock.go b/mock.go index 3b05065..7c2764d 100644 --- a/mock.go +++ b/mock.go @@ -1,4 +1,4 @@ -// Copyright (c) 2023, The GoKit Authors +// Copyright (c) 2025, The GoKit Authors // MIT License // All rights reserved. diff --git a/noop/noop.go b/noop/noop.go new file mode 100644 index 0000000..3870009 --- /dev/null +++ b/noop/noop.go @@ -0,0 +1,12 @@ +package noop + +import ( + "github.com/goxkit/configs" + "go.uber.org/zap" + + zapInstance "github.com/goxkit/logging/zap" +) + +func Install(cfgs *configs.Configs) (*zap.Logger, error) { + return zapInstance.NewStdoutZapLogger(cfgs) +} diff --git a/otlp/otlp.go b/otlp/otlp.go new file mode 100644 index 0000000..06f213a --- /dev/null +++ b/otlp/otlp.go @@ -0,0 +1,53 @@ +package otlp + +import ( + "context" + + "github.com/goxkit/configs" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc" + "go.opentelemetry.io/otel/log/global" + "go.opentelemetry.io/otel/sdk/log" + "go.opentelemetry.io/otel/sdk/resource" + semconv "go.opentelemetry.io/otel/semconv/v1.4.0" + "go.uber.org/zap" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + + zapInstance "github.com/goxkit/logging/zap" +) + +func Install(cfgs *configs.Configs) (*zap.Logger, error) { + ctx := context.Background() + + exp, err := otlploggrpc.New( + ctx, + otlploggrpc.WithEndpoint(cfgs.OTLPConfigs.Endpoint), + otlploggrpc.WithInsecure(), + otlploggrpc.WithTimeout(0), + otlploggrpc.WithCompressor("gzip"), + otlploggrpc.WithDialOption(grpc.WithTransportCredentials(insecure.NewCredentials())), + ) + if err != nil { + return nil, err + } + + processor := log.NewBatchProcessor(exp) + provider := log.NewLoggerProvider( + log.WithProcessor(processor), + log.WithResource(resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceNameKey.String(cfgs.AppConfigs.Name), + semconv.ServiceNamespaceKey.String(cfgs.AppConfigs.Namespace), + attribute.String("service.environment", cfgs.AppConfigs.Environment.String()), + semconv.DeploymentEnvironmentKey.String(cfgs.AppConfigs.Environment.String()), + semconv.TelemetrySDKLanguageKey.String("go"), + semconv.TelemetrySDKLanguageGo.Key.Bool(true), + )), + ) + + global.SetLoggerProvider(provider) + cfgs.LoggerProvider = provider + + return zapInstance.NewZapLogger(cfgs, provider) +} diff --git a/stdout/stdout.go b/stdout/stdout.go new file mode 100644 index 0000000..f4f392e --- /dev/null +++ b/stdout/stdout.go @@ -0,0 +1 @@ +package stdout diff --git a/zap/zap.go b/zap/zap.go new file mode 100644 index 0000000..ce9b4ec --- /dev/null +++ b/zap/zap.go @@ -0,0 +1,97 @@ +package zap + +import ( + "os" + + "github.com/goxkit/configs" + "go.opentelemetry.io/contrib/bridges/otelzap" + "go.opentelemetry.io/otel/sdk/log" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +func NewZapLogger(cfgs *configs.Configs, provider *log.LoggerProvider) (*zap.Logger, error) { + encoderCfg := zap.NewProductionEncoderConfig() + encoderCfg.EncodeTime = zapcore.ISO8601TimeEncoder + fmtEncoder := zapcore.NewJSONEncoder(encoderCfg) + + if cfgs.AppConfigs.Environment == configs.DevelopmentEnv || + cfgs.AppConfigs.Environment == configs.QaEnv || + cfgs.AppConfigs.Environment == configs.LocalEnv || + cfgs.AppConfigs.Environment == configs.UnknownEnv { + encoderCfg.EncodeLevel = zapcore.CapitalColorLevelEncoder + fmtEncoder = zapcore.NewConsoleEncoder(encoderCfg) + } + + stdout := zapcore.AddSync(os.Stdout) + minLevel := mapZapLogLevel(cfgs.AppConfigs) + defaultCore := zapcore.NewCore(fmtEncoder, stdout, minLevel) + + otelCore := otelzap.NewCore( + cfgs.AppConfigs.Name, + otelzap.WithLoggerProvider(provider), + ) + + combinedCore := zapcore.NewTee(defaultCore, otelCore) + + logger := zap. + New(combinedCore, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel)). + Named(cfgs.AppConfigs.Name) + + return logger, nil +} + +func NewStdoutZapLogger(cfgs *configs.Configs) (*zap.Logger, error) { + zapLogLevel := mapZapLogLevel(cfgs.AppConfigs) + + if cfgs.AppConfigs.Environment == configs.ProductionEnv || cfgs.AppConfigs.Environment == configs.StagingEnv { + logConfig := zap.NewProductionEncoderConfig() + logConfig.EncodeTime = zapcore.ISO8601TimeEncoder + encoder := zapcore.NewJSONEncoder(logConfig) + + cfgs.Logger = zap.New( + zapcore.NewCore( + encoder, + zapcore.AddSync(os.Stdout), + zapLogLevel, + ), + ). + Named(cfgs.AppConfigs.Name) + + return cfgs.Logger, nil + } + + logConfig := zap.NewDevelopmentEncoderConfig() + logConfig.EncodeTime = zapcore.ISO8601TimeEncoder + logConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder + consoleEncoder := zapcore.NewConsoleEncoder(logConfig) + + cfgs.Logger = zap.New( + zapcore.NewCore( + consoleEncoder, + zapcore.AddSync(os.Stdout), + zapLogLevel, + ), + ).Named(cfgs.AppConfigs.Name) + + return cfgs.Logger, nil +} + +// mapZapLogLevel converts the application config log level to the corresponding +// Zap log level. It defaults to InfoLevel if the level is not recognized. +func mapZapLogLevel(e *configs.AppConfigs) zapcore.Level { + switch e.LogLevel { + case configs.DEBUG: + return zap.DebugLevel + case configs.INFO: + return zap.InfoLevel + case configs.WARN: + return zap.WarnLevel + case configs.ERROR: + return zap.ErrorLevel + case configs.PANIC: + return zap.PanicLevel + default: + return zap.InfoLevel + } +}