create.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. package logger
  2. import (
  3. "fmt"
  4. "os"
  5. "path/filepath"
  6. "strings"
  7. "time"
  8. "github.com/alecthomas/units"
  9. )
  10. // Option is to encaspulate actions that will be called by Parse and run later to build an Options struct
  11. type Option func(*Options) error
  12. // Options is use to set logging configuration data
  13. type Options struct {
  14. logFileDirectory string
  15. maxFileSize units.Base2Bytes
  16. maxFileCount uint
  17. terminalOutputDisabled bool
  18. supportedFileLevels []Level
  19. supportedTerminalLevels []Level
  20. }
  21. // DisableTerminal stops terminal output for the logger
  22. func DisableTerminal(disable bool) Option {
  23. return func(c *Options) error {
  24. c.terminalOutputDisabled = disable
  25. return nil
  26. }
  27. }
  28. // File sets a custom file to log events
  29. func File(path string, size units.Base2Bytes, count uint) Option {
  30. return func(c *Options) error {
  31. c.logFileDirectory = path
  32. c.maxFileSize = size
  33. c.maxFileCount = count
  34. return nil
  35. }
  36. }
  37. // DefaultFile configures the log options will the defaults
  38. func DefaultFile(directoryPath string) Option {
  39. return func(c *Options) error {
  40. size, err := units.ParseBase2Bytes("1MB")
  41. if err != nil {
  42. return err
  43. }
  44. c.logFileDirectory = directoryPath
  45. c.maxFileSize = size
  46. c.maxFileCount = 5
  47. return nil
  48. }
  49. }
  50. // SupportedFileLevels sets the supported logging levels for the log file
  51. func SupportedFileLevels(supported []Level) Option {
  52. return func(c *Options) error {
  53. c.supportedFileLevels = supported
  54. return nil
  55. }
  56. }
  57. // SupportedTerminalevels sets the supported logging levels for the terminal output
  58. func SupportedTerminalevels(supported []Level) Option {
  59. return func(c *Options) error {
  60. c.supportedTerminalLevels = supported
  61. return nil
  62. }
  63. }
  64. // LogLevelString sets the supported logging levels from a command line flag
  65. func LogLevelString(level string) Option {
  66. return func(c *Options) error {
  67. supported, err := ParseLevelString(level)
  68. if err != nil {
  69. return err
  70. }
  71. c.supportedFileLevels = supported
  72. c.supportedTerminalLevels = supported
  73. return nil
  74. }
  75. }
  76. // Parse builds the Options struct so the caller knows what actions should be run
  77. func Parse(opts ...Option) (*Options, error) {
  78. options := &Options{}
  79. for _, opt := range opts {
  80. if err := opt(options); err != nil {
  81. return nil, err
  82. }
  83. }
  84. return options, nil
  85. }
  86. // New setups a new logger based on the options.
  87. // The default behavior is to write to standard out
  88. func New(opts ...Option) (*OutputWriter, error) {
  89. options, err := Parse(opts...)
  90. if err != nil {
  91. return nil, err
  92. }
  93. l := NewOutputWriter(SharedWriteManager)
  94. if options.logFileDirectory != "" {
  95. l.Add(NewFileRollingWriter(SanitizeLogPath(options.logFileDirectory),
  96. "cloudflared",
  97. int64(options.maxFileSize),
  98. options.maxFileCount),
  99. NewDefaultFormatter(time.RFC3339Nano), options.supportedFileLevels...)
  100. }
  101. if !options.terminalOutputDisabled {
  102. terminalFormatter := NewTerminalFormatter(time.RFC3339)
  103. if len(options.supportedTerminalLevels) == 0 {
  104. l.Add(os.Stderr, terminalFormatter, InfoLevel, ErrorLevel, FatalLevel)
  105. } else {
  106. l.Add(os.Stderr, terminalFormatter, options.supportedTerminalLevels...)
  107. }
  108. }
  109. return l, nil
  110. }
  111. // ParseLevelString returns the expected log levels based on the cmd flag
  112. func ParseLevelString(lvl string) ([]Level, error) {
  113. switch strings.ToLower(lvl) {
  114. case "fatal":
  115. return []Level{FatalLevel}, nil
  116. case "error":
  117. return []Level{FatalLevel, ErrorLevel}, nil
  118. case "info", "warn":
  119. return []Level{FatalLevel, ErrorLevel, InfoLevel}, nil
  120. case "debug":
  121. return []Level{FatalLevel, ErrorLevel, InfoLevel, DebugLevel}, nil
  122. }
  123. return []Level{}, fmt.Errorf("not a valid log level: %q", lvl)
  124. }
  125. // SanitizeLogPath checks that the logger log path
  126. func SanitizeLogPath(path string) string {
  127. newPath := strings.TrimSpace(path)
  128. // make sure it has a log file extension and is not a directory
  129. if filepath.Ext(newPath) != ".log" && !(isDirectory(newPath) || strings.HasSuffix(newPath, "/")) {
  130. newPath = newPath + ".log"
  131. }
  132. return newPath
  133. }