pkg/logger/logger.go (185 lines of code) (raw):
// Copyright Amazon.com, Inc. or its affiliates. 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 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.
package logger
import (
"os"
"runtime"
"strings"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
)
const (
defaultLogFilePath = "/var/log/aws-routed-eni/ebpf-sdk.log"
defaultLogLevel = "Debug"
envLogLevel = "AWS_EBPF_SDK_LOGLEVEL"
envLogFilePath = "AWS_EBPF_SDK_LOG_FILE"
)
// Log is global variable so that log functions can be directly accessed
var log Logger
// Fields Type to pass when we want to call WithFields for structured logging
type Fields map[string]interface{}
// Logger is our contract for the logger
type Logger interface {
Debugf(format string, args ...interface{})
Debug(format string)
Infof(format string, args ...interface{})
Info(format string)
Warnf(format string, args ...interface{})
Warn(format string)
Errorf(format string, args ...interface{})
Error(format string)
Fatalf(format string, args ...interface{})
Panicf(format string, args ...interface{})
WithFields(keyValues Fields) Logger
}
// Configuration stores the config for the logger
type Configuration struct {
LogLevel string
LogLocation string
}
// LoadLogConfig returns the log configuration
func LoadLogConfig() *Configuration {
return &Configuration{
LogLevel: GetLogLevel(),
LogLocation: GetLogLocation(),
}
}
// GetLogLocation returns the log file path
func GetLogLocation() string {
logFilePath := os.Getenv(envLogFilePath)
if logFilePath == "" {
logFilePath = defaultLogFilePath
}
return logFilePath
}
// GetLogLevel returns the log level
func GetLogLevel() string {
logLevel := os.Getenv(envLogLevel)
switch logLevel {
case "":
logLevel = defaultLogLevel
return logLevel
default:
return logLevel
}
}
type structuredLogger struct {
zapLogger *zap.SugaredLogger
}
// getZapLevel converts log level string to zapcore.Level
func getZapLevel(inputLogLevel string) zapcore.Level {
lvl := strings.ToLower(inputLogLevel)
switch lvl {
case "debug":
return zapcore.DebugLevel
case "info":
return zapcore.InfoLevel
case "warn":
return zapcore.WarnLevel
case "error":
return zapcore.ErrorLevel
case "fatal":
return zapcore.FatalLevel
default:
return zapcore.DebugLevel
}
}
func (logf *structuredLogger) Debugf(format string, args ...interface{}) {
logf.zapLogger.Debugf(format, args...)
}
func (logf *structuredLogger) Debug(format string) {
logf.zapLogger.Desugar().Debug(format)
}
func (logf *structuredLogger) Infof(format string, args ...interface{}) {
logf.zapLogger.Infof(format, args...)
}
func (logf *structuredLogger) Info(format string) {
logf.zapLogger.Desugar().Info(format)
}
func (logf *structuredLogger) Warnf(format string, args ...interface{}) {
logf.zapLogger.Warnf(format, args...)
}
func (logf *structuredLogger) Warn(format string) {
logf.zapLogger.Desugar().Warn(format)
}
func (logf *structuredLogger) Error(format string) {
logf.zapLogger.Desugar().Error(format)
}
func (logf *structuredLogger) Errorf(format string, args ...interface{}) {
logf.zapLogger.Errorf(format, args...)
}
func (logf *structuredLogger) Fatalf(format string, args ...interface{}) {
logf.zapLogger.Fatalf(format, args...)
}
func (logf *structuredLogger) Panicf(format string, args ...interface{}) {
logf.zapLogger.Fatalf(format, args...)
}
func (logf *structuredLogger) WithFields(fields Fields) Logger {
var f = make([]interface{}, 0)
for k, v := range fields {
f = append(f, k)
f = append(f, v)
}
newLogger := logf.zapLogger.With(f...)
return &structuredLogger{newLogger}
}
func getEncoder() zapcore.Encoder {
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
return zapcore.NewJSONEncoder(encoderConfig)
}
func (logConfig *Configuration) newZapLogger() *structuredLogger {
var cores []zapcore.Core
logLevel := getZapLevel(logConfig.LogLevel)
writer := getLogFilePath(logConfig.LogLocation)
cores = append(cores, zapcore.NewCore(getEncoder(), writer, logLevel))
combinedCore := zapcore.NewTee(cores...)
logger := zap.New(combinedCore,
zap.AddCaller(),
zap.AddCallerSkip(2),
)
defer logger.Sync()
sugar := logger.Sugar()
return &structuredLogger{
zapLogger: sugar,
}
}
// getLogFilePath returns the writer
func getLogFilePath(logFilePath string) zapcore.WriteSyncer {
var writer zapcore.WriteSyncer
if logFilePath == "" {
writer = zapcore.Lock(os.Stderr)
} else if strings.ToLower(logFilePath) != "stdout" {
writer = getLogWriter(logFilePath)
} else {
writer = zapcore.Lock(os.Stdout)
}
return writer
}
// getLogWriter is for lumberjack
func getLogWriter(logFilePath string) zapcore.WriteSyncer {
lumberJackLogger := &lumberjack.Logger{
Filename: logFilePath,
MaxSize: 100,
MaxBackups: 5,
MaxAge: 30,
Compress: true,
}
return zapcore.AddSync(lumberJackLogger)
}
// DefaultLogger creates and returns a new default logger.
func DefaultLogger() Logger {
productionConfig := zap.NewProductionConfig()
productionConfig.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
productionConfig.EncoderConfig.EncodeCaller = func(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder) {
_, caller.File, caller.Line, _ = runtime.Caller(8)
enc.AppendString(caller.FullPath())
}
logger, _ := productionConfig.Build()
defer logger.Sync()
sugar := logger.Sugar()
return &structuredLogger{
zapLogger: sugar,
}
}
// Get returns an default instance of the zap logger
func Get() Logger {
if log == nil {
logConfig := LoadLogConfig()
log = New(logConfig)
log.Info("Initialized new logger as an existing instance was not found")
}
return log
}
// New logger initializes logger
func New(inputLogConfig *Configuration) Logger {
log = inputLogConfig.newZapLogger()
log.Info("Constructed new logger instance")
return log
}