TIL 15 - TOML 파일과 Log, Flag Package

프동프동·2023년 2월 10일
0

TIL

목록 보기
15/46
post-thumbnail

TOML

TOML v0.5.0

toml

GitHub - pelletier/go-toml: Go library for the TOML file format

명확한 의미를 갖으며, 읽기 쉬운 최소한의 구성 파일 형식을 목표로 삼고 있다.
해시 테이블에 분명하게 대응되도록 설계되어 있다.

  • 특징
    • 대소문자를 구분한다.
    • UTF-8로 인코딩된 유니코드 문서여야한다.
    • 공백은 탭(0x09)이나 공백(0x20)을 의미한다.
    • 개행은 LF(0x0A)나 CRLF(0x0D0A)를 의미한다.
    • .toml 확장자를 사용해야한다.
    • 인터넷을 통해 전송할 경우, MIME 타입으로 application/toml을 사용한다.
  • JSON과 차이점
    • JSON
      • 주로 컴퓨터 프로글매에서 읽고 쓰는 데이터를 직렬화하는데 적합하다.
    • TOML
      • 사람이 읽고 쓰는데 쉽다는 점
      • 다른 프로그램으로 데이터를 전송할 때에는 아무런 도움이 되지 않지만, 손으로 편집할 수 있는 구성 파일에는 매우 유용하다.
  • 사용 방법
    import "github.com/pelletier/go-toml/v2"
    • TOML 문서를 읽고 저장할 구조를 정해준다
      type MyConfig struct {
            Version int
            Name    string
            Tags    []string
      }
    • Unmarshaling : TOML 문서를 읽고 Go structure를 내용으로 채운다.
      doc := `
      version = 2
      name = "go-toml"
      tags = ["go", "toml"]
      `
      
      // TOML 구조를 저장할 구조체 인스턴스 생성
      var cfg MyConfig
      // TOML 내용을 읽어 cfg 구조체에 채워넣는다.
      err := toml.Unmarshal([]byte(doc), &cfg)
      if err != nil {
            panic(err)
      }
      fmt.Println("version:", cfg.Version)
      fmt.Println("name:", cfg.Name)
      fmt.Println("tags:", cfg.Tags)
    • Marshaling : Unmarshaling과 반대, Go structure를 TOML 문서 형식으로 바꾼다.
      cfg := MyConfig{
            Version: 2,
            Name:    "go-toml",
            Tags:    []string{"go", "toml"},
      }
      
      b, err := toml.Marshal(cfg)
      if err != nil {
            panic(err)
      }
      fmt.Println(string(b))

toml 파싱

  • 설치
  • 사용 방법
    • config.toml 파일의 기준으로 구조체 선언
      type Work struct {
      	Name string
      	Desc string
      	Excute string
      	Duration int
      	Args string
      }
      
      type Config struct {
      	Server struct{
      		Mode string
      		Port string
      	}
      
      	DB map[string]map[string]interface{}
      
      	Work []Work
      }
    • 파일 오픈,파싱
      func GetConfig(fpath string)
      	c := new(Config)
      
      	if file, err := os.Open(fpath); err != nil {
      		panic(err)
      	} else {
      		defer file.Close()
      		//toml 파일 디코딩
      		if err := toml.NewDecoder(file).Decode(c); err != nil {
      			panic(err)
      		} else {
      			fmt.Println(c)
      			return c
      		}
      	}

Flag

서버 실행 시 Command Line에서 지정한 옵션을 읽어 Argument Handling을 위해 사용한다.

  • 사용 패키지
    • flag 내장 패키지 사용
  • 사용 방법
    func main() {
    	flag.Parse()
    	fmt.Println(flag.Args())
    }
    • 실행
      • go run main.go start print
    • 디폴트 값 선언 사용
      func main() {
      	var port string
      	flag.StringVar(&port, "port", "7070", "port to listen on")
      
      	var conf string
      	flag.StringVar(&conf, "config", "./conf.toml", "config file to use")
      
      	pMod := flag.String("mode", "debug", "service mode")
      	flag.Parse()
      	fmt.Println(port)
      	fmt.Println(conf)
      	fmt.Println(*pMod)
      }

Log 출력

  • 특징
    • 서버 동작에 대한 확인이나 유저 행동 분석, 보안 문제 탐지 등에 사용
    • 개발시에는 디버깅용으로 활용
    • 문제가 되지 않는 선에서 최대한 상세한 로그를 남기는 것을 권장
    • 로그는 내부 규정이나 전체 플랫폼의 템플릿에 맞춰 로그형식을 구성하는 것을 권장
  • 사용 패키지, 패키지 설치 및 import
    go get "github.com/natefinch/lumberjack"
    go get "go.uber.org/zap"
    go get "go.uber.org/zap/zapcore"
    GitHub - uber-go/zap: Blazing fast, structured, leveled logging in Go.
  • 사용 방법
    • logger.go
      package logger
      ~
      // 전역 로거
      var lg *zap.Logger
      //로거 초기화 컨피그 파라메터
      func InitLogger(cfg *conf.Config) (err error) {
      	cf := cfg.Log
      
      	now := time.Now()
      	lPath := fmt.Sprintf("%s_%s.log", cf.Fpath, now.Format("2006-01-02"))
      	// 설정 옵션
      	writeSyncer := getLogWriter(lPath, cf.Msize, cf.Mbackup, cf.Mage)
      	encoder := getEncoder()
      	var l = new(zapcore.Level)
      	err = l.UnmarshalText([]byte(cf.Level))
      	if err != nil {
      		return
      	}
      	core := zapcore.NewCore(encoder, writeSyncer, l)
      	// lg 생성
      	lg = zap.New(core, zap.AddCaller())
      	zap.ReplaceGlobals(lg)
      	return
      }
      
      func Debug(ctx ...interface{}) {
      	var b bytes.Buffer
      	for _, str := range ctx {
      		b.WriteString(str.(string))
      		b.WriteString(" ")
      	}
      
      	lg.Debug("debug", zap.String("-", b.String()))
      }
      
      // Info is a convenient alias for Root().Info
      func Info(ctx ...interface{}) {
      ~
      	lg.Info("info", zap.String("-", b.String()))
      }
      
      // Warn is a convenient alias for Root().Warn
      func Warn(ctx ...interface{}) {
      ~
      	lg.Warn("warn", zap.String("-", b.String()))
      }
      
      // Error is a convenient alias for Root().Error
      func Error(ctx ...interface{}) {
      ~
      	lg.Error("error", zap.String("-", b.String()))
      }
      
      // encoder 옵션 설정
      func getEncoder() zapcore.Encoder {
      	encoderConfig := zap.NewProductionEncoderConfig()
      	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
      	encoderConfig.TimeKey = "time"
      	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
      	encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder
      	encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
      	return zapcore.NewJSONEncoder(encoderConfig)
      }
      
      func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer {
      	lumberJackLogger := &lumberjack.Logger{
      		Filename:   filename,  // 로그파일 명 지정
      		MaxSize:    maxSize,   // 로그 파일 최대 사이즈
      		MaxBackups: maxBackup, // 최대 로그파일 수
      		MaxAge:     maxAge,    // 로그파일 저장 일수
      	}
      	return zapcore.AddSync(lumberJackLogger)
      }
      
      // gin 로거 대체 설정
      func GinLogger() gin.HandlerFunc {
      	return func(c *gin.Context) {
      		start := time.Now()
      		path := c.Request.URL.Path
      		query := c.Request.URL.RawQuery
      		c.Next()
      
      		cost := time.Since(start)
      		lg.Info(path,
      			zap.Int("status", c.Writer.Status()),
      			zap.String("method", c.Request.Method),
      			zap.String("path", path),
      			zap.String("query", query),
      			zap.String("ip", c.ClientIP()),
      			zap.String("user-agent", c.Request.UserAgent()),
      			zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
      			zap.Duration("cost", cost),
      		)
      	}
      }
      
      // gin 리커버리 대체 설정
      func GinRecovery(stack bool) gin.HandlerFunc {
      	return func(c *gin.Context) {
      		defer func() {
      			if err := recover(); err != nil {
      				// Check for a broken connection, as it is not really a
      				// condition that warrants a panic stack trace.
      				var brokenPipe bool
      				if ne, ok := err.(*net.OpError); ok {
      					if se, ok := ne.Err.(*os.SyscallError); ok {
      						if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
      							brokenPipe = true
      						}
      					}
      				}
      
      				httpRequest, _ := httputil.DumpRequest(c.Request, false)
      				if brokenPipe {
      					lg.Error(c.Request.URL.Path,
      						zap.Any("error", err),
      						zap.String("request", string(httpRequest)),
      					)
      					// If the connection is dead, we can't write a status to it.
      					c.Error(err.(error)) // nolint: errcheck
      					c.Abort()
      					return
      				}
      
      				if stack {
      					lg.Error("[Recovery from panic]",
      						zap.Any("error", err),
      						zap.String("request", string(httpRequest)),
      						zap.String("stack", string(debug.Stack())),
      					)
      				} else {
      					lg.Error("[Recovery from panic]",
      						zap.Any("error", err),
      						zap.String("request", string(httpRequest)),
      					)
      				}
      				c.AbortWithStatus(http.StatusInternalServerError)
      			}
      		}()
      		c.Next()
      	}
      }
    • main.go
      	var configFlag = flag.String("config", "./conf/config.toml", "toml file to use for configuration")
      	flag.Parse()
      	cf := conf.NewConfig(*configFlag)
      
        // ....
      
      	// 로그 초기화
      	if err := logger.InitLogger(cf); err != nil {
      		fmt.Printf("init logger failed, err:%v\n", err)
      		return
      	}
      
      	logger.Debug("ready server....")
    • route.go
      
      func (p *Router) Idx() *gin.Engine {
      	// 컨피그나 상황에 맞게 gin 모드 설정
      	gin.SetMode(gin.ReleaseMode)
      	// gin.SetMode(gin.DebugMode)
      
      	e := gin.Default()
      	// e.Use(gin.Logger())
      	// e.Use(gin.Recovery())
        // 기존의 logger, recovery 대신 logger에서 선언한 미들웨어 사용
      	e.Use(logger.GinLogger())
      	e.Use(logger.GinRecovery(true))
      	e.Use(CORS())
      
      	logger.Info("start server")
      	e.GET("/health")
      
      	account := e.Group("acc/v01", liteAuth())
      	{
      		fmt.Println(account)
      	}
      
      	return e
      }
      
profile
좋은 개발자가 되고싶은

0개의 댓글