github.com/abolfazlbeh/zhycan@v0.0.0-20230819144214-24cf38237387/internal/logger/zap_wrapper.go (about)

     1  package logger
     2  
     3  // Imports needed list
     4  import (
     5  	"errors"
     6  	"fmt"
     7  	"github.com/abolfazlbeh/zhycan/internal/config"
     8  	"github.com/abolfazlbeh/zhycan/internal/utils"
     9  	"go.uber.org/zap"
    10  	"go.uber.org/zap/zapcore"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  	"sync"
    15  )
    16  
    17  // Mark: ZapWrapper
    18  
    19  // ZapWrapper structure - implements Logger interface
    20  type ZapWrapper struct {
    21  	name            string
    22  	serviceName     string
    23  	logger          *zap.Logger
    24  	ch              chan LogObject
    25  	initialized     bool
    26  	wg              sync.WaitGroup
    27  	operationType   string
    28  	supportedOutput []string
    29  }
    30  
    31  // Constructor - It initializes the logger configuration params
    32  func (l *ZapWrapper) Constructor(name string) error {
    33  	l.wg.Add(1)
    34  	defer l.wg.Done()
    35  
    36  	l.name = name
    37  	l.serviceName = config.GetManager().GetName()
    38  	l.operationType = config.GetManager().GetOperationType()
    39  	l.supportedOutput = []string{"console", "file"}
    40  	l.initialized = false
    41  
    42  	channelSize, err := config.GetManager().Get(l.name, "channel_size")
    43  	if err != nil {
    44  		return err
    45  	}
    46  
    47  	options, err := config.GetManager().Get(l.name, "options")
    48  	if err != nil {
    49  		return err
    50  	}
    51  
    52  	var optionArray []string
    53  	for _, v := range options.([]interface{}) {
    54  		optionArray = append(optionArray, v.(string))
    55  	}
    56  
    57  	outputs, err := config.GetManager().Get(l.name, "outputs")
    58  	if err != nil {
    59  		return err
    60  	}
    61  
    62  	var outputArray []string
    63  	for _, v := range outputs.([]interface{}) {
    64  		outputArray = append(outputArray, v.(string))
    65  	}
    66  
    67  	l.ch = make(chan LogObject, int(channelSize.(float64)))
    68  
    69  	if l.operationType == "prod" {
    70  		productionEncoderConfig := zap.NewProductionEncoderConfig()
    71  		productionEncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
    72  		productionEncoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
    73  
    74  		var cores []zapcore.Core
    75  		for _, outputItem := range outputArray {
    76  			if utils.ArrayContains(&l.supportedOutput, outputItem) {
    77  				if outputItem == "console" {
    78  					level := zapcore.DebugLevel
    79  
    80  					key := fmt.Sprintf("%s.level", outputItem)
    81  					levelStr, err := config.GetManager().Get(l.name, key)
    82  					if err == nil {
    83  						level, err = zapcore.ParseLevel(levelStr.(string))
    84  						if err != nil {
    85  							continue
    86  						}
    87  					}
    88  
    89  					consoleEncoder := zapcore.NewConsoleEncoder(productionEncoderConfig)
    90  					c := zapcore.NewCore(consoleEncoder, zapcore.AddSync(os.Stdout), level)
    91  					cores = append(cores, c)
    92  				} else if outputItem == "file" {
    93  					level := zapcore.DebugLevel
    94  
    95  					key := fmt.Sprintf("%s.level", outputItem)
    96  					levelStr, err := config.GetManager().Get(l.name, key)
    97  					if err == nil {
    98  						level, err = zapcore.ParseLevel(levelStr.(string))
    99  						if err != nil {
   100  							continue
   101  						}
   102  					}
   103  
   104  					// Read the root path of logs
   105  					path := "logs"
   106  					key = fmt.Sprintf("%s.path", outputItem)
   107  					pathStr, err := config.GetManager().Get(l.name, key)
   108  					if err == nil {
   109  						if strings.TrimSpace(pathStr.(string)) != "" {
   110  							path = strings.TrimSpace(pathStr.(string))
   111  						}
   112  					}
   113  
   114  					// Check the directory existed, If not create all the nested directories
   115  					if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
   116  						err1 := os.MkdirAll(path, os.ModePerm)
   117  						if err1 != nil {
   118  							continue
   119  						}
   120  					}
   121  
   122  					expectLogPath := filepath.Join(path, fmt.Sprintf("%s.log", config.GetManager().GetName()))
   123  					logFile, err := os.OpenFile(expectLogPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModePerm)
   124  					if err != nil {
   125  						continue
   126  					}
   127  					writer := zapcore.AddSync(logFile)
   128  					fileEncoder := zapcore.NewJSONEncoder(productionEncoderConfig)
   129  
   130  					c := zapcore.NewCore(fileEncoder, writer, level)
   131  					cores = append(cores, c)
   132  				}
   133  			}
   134  		}
   135  
   136  		core := zapcore.NewTee(
   137  			cores...,
   138  		)
   139  
   140  		if utils.ArrayContains(&optionArray, "stackTrace") && utils.ArrayContains(&optionArray, "caller") {
   141  			l.logger = zap.New(core, zap.AddStacktrace(zapcore.ErrorLevel), zap.AddCaller())
   142  		} else if utils.ArrayContains(&optionArray, "stackTrace") {
   143  			l.logger = zap.New(core, zap.AddStacktrace(zapcore.ErrorLevel))
   144  		} else if utils.ArrayContains(&optionArray, "caller") {
   145  			l.logger = zap.New(core, zap.AddCaller())
   146  		} else {
   147  			l.logger = zap.New(core)
   148  		}
   149  	} else { // otherwise we create a development version (`dev`, `test`, ...)
   150  		developmentEncoderConfig := zap.NewDevelopmentEncoderConfig()
   151  		developmentEncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
   152  		developmentEncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
   153  
   154  		var cores []zapcore.Core
   155  		for _, outputItem := range outputArray {
   156  			if utils.ArrayContains(&l.supportedOutput, outputItem) {
   157  				if outputItem == "console" {
   158  					level := zapcore.DebugLevel
   159  
   160  					key := fmt.Sprintf("%s.level", outputItem)
   161  					levelStr, err := config.GetManager().Get(l.name, key)
   162  					if err == nil {
   163  						level, err = zapcore.ParseLevel(levelStr.(string))
   164  						if err != nil {
   165  							continue
   166  						}
   167  					}
   168  
   169  					consoleEncoder := zapcore.NewConsoleEncoder(developmentEncoderConfig)
   170  					c := zapcore.NewCore(consoleEncoder, zapcore.AddSync(os.Stdout), level)
   171  
   172  					cores = append(cores, c)
   173  				} else if outputItem == "file" {
   174  					level := zapcore.DebugLevel
   175  
   176  					key := fmt.Sprintf("%s.level", outputItem)
   177  					levelStr, err := config.GetManager().Get(l.name, key)
   178  					if err == nil {
   179  						level, err = zapcore.ParseLevel(levelStr.(string))
   180  						if err != nil {
   181  							continue
   182  						}
   183  					}
   184  
   185  					// Read the root path of logs
   186  					path := "logs"
   187  					key = fmt.Sprintf("%s.path", outputItem)
   188  					pathStr, err := config.GetManager().Get(l.name, key)
   189  					if err == nil {
   190  						if strings.TrimSpace(pathStr.(string)) != "" {
   191  							path = strings.TrimSpace(pathStr.(string))
   192  						}
   193  					}
   194  
   195  					// Check the directory existed, If not create all the nested directories
   196  					if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
   197  						err1 := os.MkdirAll(path, os.ModePerm)
   198  						if err1 != nil {
   199  							continue
   200  						}
   201  					}
   202  
   203  					expectLogPath := filepath.Join(path, fmt.Sprintf("%s.log", config.GetManager().GetName()))
   204  					logFile, err := os.OpenFile(expectLogPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModePerm)
   205  					if err != nil {
   206  						continue
   207  					}
   208  					writer := zapcore.AddSync(logFile)
   209  					fileEncoder := zapcore.NewJSONEncoder(developmentEncoderConfig)
   210  
   211  					c := zapcore.NewCore(fileEncoder, writer, level)
   212  					cores = append(cores, c)
   213  				}
   214  			}
   215  		}
   216  
   217  		core := zapcore.NewTee(
   218  			cores...,
   219  		)
   220  
   221  		if utils.ArrayContains(&optionArray, "stackTrace") && utils.ArrayContains(&optionArray, "caller") {
   222  			l.logger = zap.New(core, zap.AddStacktrace(zapcore.ErrorLevel), zap.AddCaller())
   223  		} else if utils.ArrayContains(&optionArray, "stackTrace") {
   224  			l.logger = zap.New(core, zap.AddStacktrace(zapcore.ErrorLevel))
   225  		} else if utils.ArrayContains(&optionArray, "caller") {
   226  			l.logger = zap.New(core, zap.AddCaller())
   227  		} else {
   228  			l.logger = zap.New(core)
   229  		}
   230  	}
   231  
   232  	go l.runner()
   233  	l.initialized = true
   234  
   235  	return nil
   236  }
   237  
   238  // Close - it closes logger channel
   239  func (l *ZapWrapper) Close() {
   240  	l.wg.Wait()
   241  
   242  	_ = l.logger.Sync()
   243  	defer close(l.ch)
   244  }
   245  
   246  // Log - write log object to the channel
   247  func (l *ZapWrapper) Log(obj *LogObject) {
   248  	l.wg.Wait()
   249  
   250  	go func(obj *LogObject) {
   251  		l.ch <- *obj
   252  	}(obj)
   253  }
   254  
   255  // IsInitialized - that returns boolean value whether it's initialized
   256  func (l *ZapWrapper) IsInitialized() bool {
   257  	return l.initialized
   258  }
   259  
   260  // Instance - returns exact logger instance
   261  func (l *ZapWrapper) Instance() *zap.Logger {
   262  	l.wg.Wait()
   263  	return l.logger
   264  }
   265  
   266  // Sync - call the sync method of the project
   267  func (l *ZapWrapper) Sync() {
   268  	l.wg.Wait()
   269  	l.logger.Sync()
   270  }
   271  
   272  // runner - the goroutine that reads from channel and process it
   273  func (l *ZapWrapper) runner() {
   274  	l.wg.Wait()
   275  	for c := range l.ch {
   276  		if c.Level.IsLogLevel() {
   277  			f := []zapcore.Field{
   278  				zap.Any("service", l.serviceName),
   279  				zap.Any("module", c.Module),
   280  				zap.Any("log_type", c.LogType),
   281  				zap.Any("time", c.Time),
   282  				zap.Any("additional", c.Additional),
   283  			}
   284  			switch c.Level {
   285  			case DEBUG:
   286  				l.logger.Debug(fmt.Sprintf("%v", c.Message), f...)
   287  				break
   288  			case INFO:
   289  				l.logger.Info(fmt.Sprintf("%v", c.Message), f...)
   290  				break
   291  			case WARNING:
   292  				l.logger.Warn(fmt.Sprintf("%v", c.Message), f...)
   293  				break
   294  			case ERROR:
   295  				l.logger.Error(fmt.Sprintf("%v", c.Message), f...)
   296  				break
   297  			}
   298  		}
   299  	}
   300  }