140 lines
4.2 KiB
Go
140 lines
4.2 KiB
Go
package logging
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
"go.opentelemetry.io/contrib/bridges/otelzap"
|
|
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc"
|
|
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp"
|
|
"go.opentelemetry.io/otel/log/global"
|
|
"go.opentelemetry.io/otel/sdk/log"
|
|
"go.uber.org/zap"
|
|
"go.uber.org/zap/zapcore"
|
|
|
|
"github.com/kanootoko/codevault/internal"
|
|
"github.com/kanootoko/codevault/internal/config"
|
|
"github.com/kanootoko/codevault/internal/observability"
|
|
)
|
|
|
|
func ConfigureLogger(ctx context.Context, cfg config.LoggingConfig, isProductionEnv bool) (logger *zap.Logger, closeFunc func(), err error) {
|
|
stderrLevel, err := cfg.Stderr.ToZapLevel()
|
|
if err != nil && !errors.Is(err, config.ErrLoggingDisabled) {
|
|
return nil, nil, fmt.Errorf("error on getting stderr logging level: %w", err)
|
|
}
|
|
fileLoggers := make(map[string]zapcore.Level)
|
|
for level, file := range cfg.Files {
|
|
l, err := level.ToZapLevel()
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("error on getting file logging level: %w", err)
|
|
}
|
|
fileLoggers[file] = l
|
|
}
|
|
var stderrEncoder zapcore.Encoder
|
|
var fileEncoder zapcore.Encoder
|
|
{
|
|
var fileLoggerCfg zapcore.EncoderConfig
|
|
var stderrLoggerCfg zapcore.EncoderConfig
|
|
if isProductionEnv {
|
|
fileLoggerCfg = zap.NewProductionEncoderConfig()
|
|
stderrLoggerCfg = zap.NewProductionEncoderConfig()
|
|
} else {
|
|
fileLoggerCfg = zap.NewDevelopmentEncoderConfig()
|
|
stderrLoggerCfg = zap.NewDevelopmentEncoderConfig()
|
|
}
|
|
fileLoggerCfg.TimeKey = "timestamp"
|
|
fileLoggerCfg.EncodeTime = zapcore.ISO8601TimeEncoder
|
|
|
|
stderrLoggerCfg.EncodeLevel = zapcore.CapitalColorLevelEncoder
|
|
|
|
stderrEncoder = zapcore.NewConsoleEncoder(stderrLoggerCfg)
|
|
fileEncoder = zapcore.NewJSONEncoder(fileLoggerCfg)
|
|
}
|
|
|
|
fileHandlers := make([]*os.File, 0, len(fileLoggers))
|
|
zapWriters := make([]zapcore.Core, 0, len(fileLoggers)+1)
|
|
for filename, level := range fileLoggers {
|
|
fileHandler, err := os.Create(filename)
|
|
if err != nil {
|
|
for _, file := range fileHandlers[:len(fileHandlers)-1] {
|
|
_ = file.Close()
|
|
}
|
|
return nil, nil, fmt.Errorf("error on log file creation: %w", err)
|
|
}
|
|
fileHandlers = append(fileHandlers, fileHandler)
|
|
fileSync := zapcore.AddSync(fileHandler)
|
|
zapWriters = append(zapWriters, zapcore.NewCore(fileEncoder, fileSync, zap.NewAtomicLevelAt(level)))
|
|
}
|
|
|
|
stderrSync := zapcore.AddSync(os.Stderr)
|
|
if stderrLevel != zapcore.InvalidLevel {
|
|
zapWriters = append(zapWriters, zapcore.NewCore(stderrEncoder, stderrSync, zap.NewAtomicLevelAt(stderrLevel)))
|
|
}
|
|
|
|
var otlpCloseFunc func()
|
|
if cfg.Exporter.Level != "" {
|
|
var otlpWriter zapcore.Core
|
|
var err error
|
|
otlpWriter, otlpCloseFunc, err = configureOtelLogging(ctx, cfg.Exporter)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("cannot configure OpenTelemetry log exporter: %w", err)
|
|
}
|
|
zapWriters = append(zapWriters, otlpWriter)
|
|
}
|
|
|
|
core := zapcore.NewTee(
|
|
zapWriters...,
|
|
)
|
|
|
|
logger = zap.New(core)
|
|
|
|
closeFunc = func() {
|
|
_ = logger.Sync()
|
|
for _, file := range fileHandlers {
|
|
_ = file.Close()
|
|
}
|
|
if otlpCloseFunc != nil {
|
|
otlpCloseFunc()
|
|
}
|
|
}
|
|
|
|
logger.Info("configured logging", zap.Any("config", cfg))
|
|
return
|
|
}
|
|
|
|
func configureOtelLogging(ctx context.Context, cfg config.LogsExporterConfig) (zapcore.Core, func(), error) {
|
|
var exporter log.Exporter
|
|
var err error
|
|
if cfg.Transport == config.HTTPTransportType {
|
|
exporter, err = otlploghttp.New(ctx, otlploghttp.WithEndpointURL(cfg.Endpoint))
|
|
} else {
|
|
exporter, err = otlploggrpc.New(ctx, otlploggrpc.WithEndpointURL(cfg.Endpoint))
|
|
}
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("cannot create OpenTelemetry logs exporter: %w", err)
|
|
}
|
|
res, err := observability.GetOtelResource()
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("cannot get OpenTelemetry resource: %w", err)
|
|
}
|
|
|
|
provider := log.NewLoggerProvider(
|
|
log.WithProcessor(log.NewBatchProcessor(exporter)),
|
|
log.WithResource(res),
|
|
)
|
|
|
|
closeFunc := func() {
|
|
shutdownContext, cancel := context.WithTimeout(context.Background(), time.Millisecond*300)
|
|
defer cancel()
|
|
_ = provider.ForceFlush(shutdownContext)
|
|
_ = provider.Shutdown(shutdownContext)
|
|
<-shutdownContext.Done()
|
|
}
|
|
global.SetLoggerProvider(provider)
|
|
|
|
return otelzap.NewCore(internal.PackageName, otelzap.WithLoggerProvider(provider)), closeFunc, nil
|
|
}
|