Разработать пакет обработки логов на основе go

задняя часть Go GitHub

Веб-фреймворк для языка go, разработанный мной недавноBingoНужна функция обработки логов в стандартной библиотеке, смотрите в стандартной библиотекеlogМешок, Я обнаружил, что функция слишком проста, поэтому я хочу перестроить колесо и извлечь его отдельно как модуль, чтобы помочь в разработке фреймворка.

[bingo-log] для завершенияbingoСторонний пакет, разработанный для функции журнала , не зависит от фреймворка и может использоваться только в других проектах.

Адрес на гитхабе:bingo-log

Установите и используйте вREADME.mdЭто было очень четко написано на , поэтому я не буду повторяться здесь, в основном для записи процесса разработки.

1. Ожидаемый эффект

Функции, которые я хочу, чтобы этот пакет содержал:

  1. Поддерживает несколько уровней отчетов об ошибках
  2. Пользовательская конфигурация журнала и автоматическая сегментация
  3. Журнал асинхронного вывода

2. Идеи реализации

Будьте готовы сделать так, чтобы этот пакет ведения журнала поддерживал (FATAL,ERROR,WARNING,DEBUG,INFO) 5 уровней ошибок,

За основу напишите структуру лога, задайте в ней данные типа интерфейса и поместите в этот интерфейс пользовательские методы, чтобы все объекты, реализующие этот интерфейс, можно было передавать в структуру лога в качестве параметров

Как реализовать асинхронную функцию?

Чтобы ограничить потребление ресурсов, используйте пул соединений сопрограммы, чтобы поместить каждый вывод в пул сопрограммы для достижения асинхронного эффекта.

Для пула соединений я не буду изобретать велосипед, а воспользуюсь существующим проектом на github:grpool

начать разработку

  1. Постройте самое простое дно: бревенчатая структура

Сначала объявите две константы для обозначения синхронного или асинхронного вывода.

const (
	LogSyncMode = iota
	LogPoolMode
)

построить структуру

type Log struct {
	Connector                    // 内嵌连接器,用来定制化功能
	sync.Mutex
	initialized     bool         // 该日志对象是否初始化
	mode            int          // 日志记录模式  同步记录 or 协程池记录
	pool            *grpool.Pool // 协程池
	poolExpiredTime int          // 协程池模式下,每个空闲协程的存活时间(秒)
	poolWorkerNum   int          // 协程池模式下,允许的最高协程数
}
  1. Создание интерфейса коннектора

Мы хотим использовать соединители для установки каждого выхода, поэтому этот интерфейс должен реализовать следующие методы.

type Connector interface {
	Fatal(message ...interface{})
	Error(message ...interface{})
	Warning(message ...interface{})
	Debug(message ...interface{})
	Info(message ...interface{})                           // 打印
	Output(message string)                                 // 将信息输出到文件中
	GetMessage(degree int, message ...interface{}) string // 将输入的信息添加抬头(例如添加打印时间等)
	GetFile(config map[string]string) *os.File             // 当前日志要输出到的文件位置,传入一个map 代表配置
}

Вышеупомянутые методы 5 - это то, что нужно сделать для уровней сообщения об ошибках 5. Главное, что нужно сделать, это вывести журнал, первый вызовGetMessage()Упакуйте информацию в нужную нам структуру, выведите вывод на консоль, а затем вызовитеOutputметод печати журнала в файл журнала

а такжеOutput()способ вызоваGetFile()способ получить указатель файла для вывода, мы можемGetFile()Способ разделения файла задается в методе, если требуется динамическое разделение, тоmapПараметр — это параметр, переданный извне.

  1. заLogМетод добавления структуры:
  • Сначала напишите, как создать объект журнала:

      func NewLog(mode int) *Log {
      	l := &Log{}
      	l.SetMode(mode)
      	l.initialize()  // 这里对结构体中的数据做初始化
      	return l
      }
    
  • затем загрузите коннектор

        // 加载连接器
        func (l *Log) LoadConnector(conn Connector) {
        	l.Connector = conn  // 所有实现了连接器接口的对象都可以作为参数传入
        }
    
  • Затем напишите 5 уровней ошибок:

       // 重写5种日志级别的打印函数
       func (l *Log) Fatal(message string) {
       	// 根据模式
       	l.exec(l.Connector.Fatal, message)
       }
       
       func (l *Log) Error(message string) {
       	l.exec(l.Connector.Error, message)
       }
       
       func (l *Log) Warning(message string) {
       	l.exec(l.Connector.Warning, message)
       }
       
       func (l *Log) Debug(message string) {
       	l.exec(l.Connector.Debug, message)
       }
       
       func (l *Log) Info(message string) {
       	l.exec(l.Connector.Info, message)
       }
    
    
  • надexecМетод заключается в выборе прямого вывода или использовании вывода пула сопрограмм в соответствии с режимом вывода:

       func (l *Log) exec(f func(message ...interface{}), message string) {
       	// 同步模式
       	if l.mode == LogSyncMode {
       		l.Lock()
       		defer l.Unlock()
       		f(message)
       	} else if l.mode == LogPoolMode { // 协程池异步模式
       		l.initialize() // 先初始化
       		l.Lock()
       		defer l.Unlock()
       		l.AddWaitCount(1)  // 向池中添加计数器,可以计算池中有多少协程正在被使用
       		l.pool.JobQueue <- func() {
       			f(message)
       			defer l.pool.JobDone()
       		}
       	}
       }
    

Как видно из приведенного выше кода,LogСтруктура отвечает только за синхронное или асинхронное выполнение, самое главное место — коннекторConnector, я реализовал дваConnector(BaseConnectorа такжеKirinConnector), затем реализуем базовый коннекторBaseConnector:

  • создать структуру

      type BaseConnector struct {
      	sync.Mutex  // 这里是因为有用到map的地方需要加锁
      }
    
  • Реализуйте интерфейс коннектора:

    1. Сначала реализуйте интерфейс GetFile, который фактически создается по текущему путиbingo.logфайл и возвращает указатель файла:
      // 返回一个文件句柄,用来写入数据
      func (b BaseConnector) GetFile(config map[string]string) *os.File { // 默认情况下,输出到当前路径下的bingo.log文件中
      	dir, err := os.Getwd()
      	if err != nil {
      		panic(err)
      	}
      	path := dir + "/bingo.log" // 真实要保存的文件位置
      	// 判断文件是否存在
      	if _, err := os.Stat(path); err != nil {
      		// 文件不存在,创建
      		f, err := os.Create(path)
      		//defer f.Close()  // 关闭操作要放在调用位置
      		if err != nil {
      			panic(err)
      		}
      		return f
      	}
      	// 打开该文件,追加模式
      	f, err := os.OpenFile(path, os.O_WRONLY, os.ModeAppend)
      
      	if err != nil {
      		panic(err)
      	}
      
      	return f
      }
    
    1. выполнитьOutputметод:
       
      func (b BaseConnector) Output(message string) {
          // 获取到要输出的文件路径
          file := b.GetFile(make(map[string]string))
          defer file.Close()
          n, _ := file.Seek(0, os.SEEK_END)  // 向文件末尾追加数据
          // 写入数据
          file.WriteAt([]byte(message), n)
      }
    
    
    1. выполнитьGetMessageметод, здесь заключается в том, чтобы обернуть журнал для вывода в желаемый формат:
          // 输出格式为 [日志级别][时间][日志内容]         
          func (b BaseConnector) GetMessage(degree int, message ...interface{}) string {
              var title string
              switch degree {
              case FATAL:
                  title = "[FATAL] "
              case ERROR:
                  title = "[ERROR] "
              case WARNING:
                  title = "[WARNING]"
              case DEBUG:
                  title = "[DEBUG] "
              case INFO:
                  title = "[INFO]"
              default:
                  title = "[UNKNOWN]"
              }
              // 将传入的信息扩展一下
              // 默认添加当前时间
              return title + "[" + time.Now().Format("2006-01-02 15:04:05") + "] " + fmt.Sprint(message...) + "\n"
          }
    
    1. Реализует 5 уровней логирования:
       func (b BaseConnector) Info(message ...interface{}) {
       	// 绿色输出在控制台
       	m := b.GetMessage(INFO, message...)
       	fmt.Print(clcolor.Green(m))
       	// 输出在文件中
       	b.Output(m)
       }
    

    Чтобы в консоли выводить разные уровни логов разными цветами, нам нужно добавить цвета в функцию печати, конкретный способ здесьРаскрасить терминал (версии C и Golang)

    Я напрямую использую сторонний пакет, написанный кем-то другим здесьxcltapestry/xclpkg

    Использовать напрямуюclcolor.Green()Только что

    Таким образом делается базовый коннектор, и мы можем в любой момент расширить его сами.

резюме

Используйте что-то вроде:

   
  log := bingo_log.NewLog(bingo_log.LogSyncMode)

  conn := new(bingo_log.BaseConnector)

  log.LoadConnector(conn)

  log.Info("testing")
  log.Debug("testing")
  log.Warning("testing")
  log.Error("testing")
  log.Fatal("testing")

Интерфейс — чрезвычайно мощная функция golang, мы можем использовать интерфейс для завершения многих динамических структур.

Наконец, порекомендуйте свой собственный веб-фреймворк.Bingo, попросить звезду, попросить PR ~~~