Веб-фреймворк для языка go, разработанный мной недавноBingoНужна функция обработки логов в стандартной библиотеке, смотрите в стандартной библиотекеlog
Мешок,
Я обнаружил, что функция слишком проста, поэтому я хочу перестроить колесо и извлечь его отдельно как модуль, чтобы помочь в разработке фреймворка.
[bingo-log] для завершенияbingoСторонний пакет, разработанный для функции журнала , не зависит от фреймворка и может использоваться только в других проектах.
Адрес на гитхабе:bingo-log
Установите и используйте вREADME.md
Это было очень четко написано на , поэтому я не буду повторяться здесь, в основном для записи процесса разработки.
1. Ожидаемый эффект
Функции, которые я хочу, чтобы этот пакет содержал:
- Поддерживает несколько уровней отчетов об ошибках
- Пользовательская конфигурация журнала и автоматическая сегментация
- Журнал асинхронного вывода
2. Идеи реализации
Будьте готовы сделать так, чтобы этот пакет ведения журнала поддерживал (FATAL
,ERROR
,WARNING
,DEBUG
,INFO
) 5 уровней ошибок,
За основу напишите структуру лога, задайте в ней данные типа интерфейса и поместите в этот интерфейс пользовательские методы, чтобы все объекты, реализующие этот интерфейс, можно было передавать в структуру лога в качестве параметров
Как реализовать асинхронную функцию?
Чтобы ограничить потребление ресурсов, используйте пул соединений сопрограммы, чтобы поместить каждый вывод в пул сопрограммы для достижения асинхронного эффекта.
Для пула соединений я не буду изобретать велосипед, а воспользуюсь существующим проектом на github:grpool
начать разработку
- Постройте самое простое дно: бревенчатая структура
Сначала объявите две константы для обозначения синхронного или асинхронного вывода.
const (
LogSyncMode = iota
LogPoolMode
)
построить структуру
type Log struct {
Connector // 内嵌连接器,用来定制化功能
sync.Mutex
initialized bool // 该日志对象是否初始化
mode int // 日志记录模式 同步记录 or 协程池记录
pool *grpool.Pool // 协程池
poolExpiredTime int // 协程池模式下,每个空闲协程的存活时间(秒)
poolWorkerNum int // 协程池模式下,允许的最高协程数
}
- Создание интерфейса коннектора
Мы хотим использовать соединители для установки каждого выхода, поэтому этот интерфейс должен реализовать следующие методы.
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
Параметр — это параметр, переданный извне.
- за
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的地方需要加锁 }
-
Реализуйте интерфейс коннектора:
- Сначала реализуйте интерфейс 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 }
- выполнить
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) }
- выполнить
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" }
- Реализует 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()
Только чтоТаким образом делается базовый коннектор, и мы можем в любой момент расширить его сами.
- Сначала реализуйте интерфейс GetFile, который фактически создается по текущему пути
резюме
Используйте что-то вроде:
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 ~~~