logger.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653
  1. package logger
  2. import (
  3. "bytes"
  4. "errors"
  5. "fmt"
  6. "io"
  7. "log"
  8. "os"
  9. "regexp"
  10. "runtime/debug"
  11. "sort"
  12. "strings"
  13. "sync"
  14. "sync/atomic"
  15. "time"
  16. )
  17. /***
  18. author:donnie email donnie4w@gmail.com
  19. 在控制台打印:直接调用 Debug(***) Info(***) Warn(***) Error(***) Fatal(***)
  20. 可以设置打印格式:SetFormat(FORMAT_SHORTFILENAME|FORMAT_DATE|FORMAT_TIME)
  21. 无其他格式,只打印日志内容
  22. FORMAT_NANO
  23. 长文件名及行数
  24. FORMAT_LONGFILENAME
  25. 短文件名及行数
  26. FORMAT_SHORTFILENAME
  27. 精确到日期
  28. FORMAT_DATE
  29. 精确到秒
  30. FORMAT_TIME
  31. 精确到微秒
  32. FORMAT_MICROSECNDS
  33. —————————————————————————————————————————————————————————————————————
  34. 写日志文件可以获取实例
  35. 全局实例可以直接调用log := logging.GetStaticLogger()
  36. 获取新实例可以调用log := logging.NewLogger()
  37. 1. 按日期分割日志文件
  38. log.SetRollingDaily("d://foldTest", "log.txt")
  39. 2. 按文件大小分割日志文件
  40. log.SetRollingFile("d://foldTest", "log.txt", 300, MB)
  41. log.SetConsole(false)控制台不打日志,默认值true
  42. 日志级别
  43. ***/
  44. const (
  45. _VER string = "1.0.1"
  46. )
  47. type _LEVEL int8
  48. type _UNIT int64
  49. type _MODE_TIME uint8
  50. type _ROLLTYPE int //dailyRolling ,rollingFile
  51. type _FORMAT int
  52. const _DATEFORMAT_DAY = "20060102"
  53. const _DATEFORMAT_HOUR = "2006010215"
  54. const _DATEFORMAT_MONTH = "200601"
  55. var static_mu *sync.Mutex = new(sync.Mutex)
  56. var static_lo *_logger = NewLogger()
  57. const (
  58. _ = iota
  59. KB _UNIT = 1 << (iota * 10)
  60. MB
  61. GB
  62. TB
  63. )
  64. const (
  65. MODE_HOUR _MODE_TIME = 1
  66. MODE_DAY _MODE_TIME = 2
  67. MODE_MONTH _MODE_TIME = 3
  68. )
  69. const (
  70. /*无其他格式,只打印日志内容*/
  71. FORMAT_NANO _FORMAT = 0
  72. /*长文件名及行数*/
  73. FORMAT_LONGFILENAME = _FORMAT(log.Llongfile)
  74. /*短文件名及行数*/
  75. FORMAT_SHORTFILENAME = _FORMAT(log.Lshortfile)
  76. /*日期时间精确到天*/
  77. FORMAT_DATE = _FORMAT(log.Ldate)
  78. /*时间精确到秒*/
  79. FORMAT_TIME = _FORMAT(log.Ltime)
  80. /*时间精确到微秒*/
  81. FORMAT_MICROSECNDS = _FORMAT(log.Lmicroseconds)
  82. )
  83. const (
  84. /*日志级别:ALL 最低级别*/
  85. LEVEL_ALL _LEVEL = iota
  86. /*日志级别:DEBUG 小于INFO*/
  87. LEVEL_DEBUG
  88. /*日志级别:INFO 小于 WARN*/
  89. LEVEL_INFO
  90. /*日志级别:WARN 小于 ERROR*/
  91. LEVEL_WARN
  92. /*日志级别:ERROR 小于 FATAL*/
  93. LEVEL_ERROR
  94. /*日志级别:FATAL 小于 OFF*/
  95. LEVEL_FATAL
  96. /*日志级别:off 不打印任何日志*/
  97. LEVEL_OFF
  98. )
  99. const (
  100. _DAYLY _ROLLTYPE = iota
  101. _ROLLFILE
  102. )
  103. var default_format _FORMAT = FORMAT_SHORTFILENAME | FORMAT_DATE | FORMAT_TIME
  104. var default_level = LEVEL_ALL
  105. /*设置打印格式*/
  106. func SetFormat(format _FORMAT) {
  107. default_format = format
  108. static_lo.SetFormat(format)
  109. }
  110. /*设置控制台日志级别,默认ALL*/
  111. func SetLevel(level _LEVEL) {
  112. default_level = level
  113. static_lo.SetLevel(level)
  114. }
  115. func SetConsole(on bool) {
  116. static_lo.SetConsole(on)
  117. }
  118. /*获得全局Logger对象*/
  119. func GetStaticLogger() *_logger {
  120. return _staticLogger()
  121. }
  122. func SetRollingFile(fileDir, fileName string, maxFileSize int64, unit _UNIT) (err error) {
  123. return SetRollingFileLoop(fileDir, fileName, maxFileSize, unit, 0)
  124. }
  125. func SetRollingDaily(fileDir, fileName string) (err error) {
  126. return SetRollingByTime(fileDir, fileName, MODE_DAY)
  127. }
  128. func SetRollingFileLoop(fileDir, fileName string, maxFileSize int64, unit _UNIT, maxFileNum int) (err error) {
  129. return static_lo.SetRollingFileLoop(fileDir, fileName, maxFileSize, unit, maxFileNum)
  130. }
  131. func SetRollingByTime(fileDir, fileName string, mode _MODE_TIME) (err error) {
  132. return static_lo.SetRollingByTime(fileDir, fileName, mode)
  133. }
  134. func _staticLogger() *_logger {
  135. return static_lo
  136. }
  137. func Debug(v ...interface{}) {
  138. _print(default_format, LEVEL_DEBUG, default_level, 2, v...)
  139. }
  140. func Info(v ...interface{}) {
  141. _print(default_format, LEVEL_INFO, default_level, 2, v...)
  142. }
  143. func Warn(v ...interface{}) {
  144. _print(default_format, LEVEL_WARN, default_level, 2, v...)
  145. }
  146. func Error(v ...interface{}) {
  147. _print(default_format, LEVEL_ERROR, default_level, 2, v...)
  148. }
  149. func Fatal(v ...interface{}) {
  150. _print(default_format, LEVEL_FATAL, default_level, 2, v...)
  151. }
  152. func _print(_format _FORMAT, level, _default_level _LEVEL, calldepth int, v ...interface{}) {
  153. if level < _default_level {
  154. return
  155. }
  156. _staticLogger().println(level, k1(calldepth), v...)
  157. }
  158. func __print(_format _FORMAT, level, _default_level _LEVEL, calldepth int, v ...interface{}) {
  159. _console(formatV(v...), getlevelname(level, default_format), _format, k1(calldepth))
  160. }
  161. func getlevelname(level _LEVEL, format _FORMAT) (levelname string) {
  162. if format == FORMAT_NANO {
  163. return
  164. }
  165. switch level {
  166. case LEVEL_ALL:
  167. levelname = "[ALL]"
  168. case LEVEL_DEBUG:
  169. levelname = "[DEBUG]"
  170. case LEVEL_INFO:
  171. levelname = "[INFO]"
  172. case LEVEL_WARN:
  173. levelname = "[WARN]"
  174. case LEVEL_ERROR:
  175. levelname = "[ERROR]"
  176. case LEVEL_FATAL:
  177. levelname = "[FATAL]"
  178. default:
  179. }
  180. return
  181. }
  182. /*————————————————————————————————————————————————————————————————————————————*/
  183. type _logger struct {
  184. _level _LEVEL
  185. _format _FORMAT
  186. _rwLock *sync.RWMutex
  187. _safe bool
  188. _fileDir string
  189. _fileName string
  190. _maxSize int64
  191. _unit _UNIT
  192. _rolltype _ROLLTYPE
  193. _mode _MODE_TIME
  194. _fileObj *fileObj
  195. _maxFileNum int
  196. _isConsole bool
  197. }
  198. func NewLogger() (log *_logger) {
  199. log = &_logger{_level: LEVEL_DEBUG, _rolltype: _DAYLY, _rwLock: new(sync.RWMutex), _format: FORMAT_SHORTFILENAME | FORMAT_DATE | FORMAT_TIME, _isConsole: true}
  200. log.newfileObj()
  201. return
  202. }
  203. //控制台日志是否打开
  204. func (this *_logger) SetConsole(_isConsole bool) {
  205. this._isConsole = _isConsole
  206. }
  207. func (this *_logger) Debug(v ...interface{}) {
  208. this.println(LEVEL_DEBUG, 2, v...)
  209. }
  210. func (this *_logger) Info(v ...interface{}) {
  211. this.println(LEVEL_INFO, 2, v...)
  212. }
  213. func (this *_logger) Warn(v ...interface{}) {
  214. this.println(LEVEL_WARN, 2, v...)
  215. }
  216. func (this *_logger) Error(v ...interface{}) {
  217. this.println(LEVEL_ERROR, 2, v...)
  218. }
  219. func (this *_logger) Fatal(v ...interface{}) {
  220. this.println(LEVEL_FATAL, 2, v...)
  221. }
  222. func (this *_logger) SetFormat(format _FORMAT) {
  223. this._format = format
  224. }
  225. func (this *_logger) SetLevel(level _LEVEL) {
  226. this._level = level
  227. }
  228. /*按日志文件大小分割日志文件
  229. fileDir 日志文件夹路径
  230. fileName 日志文件名
  231. maxFileSize 日志文件大小最大值
  232. unit 日志文件大小单位
  233. */
  234. func (this *_logger) SetRollingFile(fileDir, fileName string, maxFileSize int64, unit _UNIT) (err error) {
  235. return this.SetRollingFileLoop(fileDir, fileName, maxFileSize, unit, 0)
  236. }
  237. /*按日志文件大小分割日志文件,指定保留的最大日志文件数
  238. fileDir 日志文件夹路径
  239. fileName 日志文件名
  240. maxFileSize 日志文件大小最大值
  241. unit 日志文件大小单位
  242. maxFileNum 留的日志文件数
  243. */
  244. func (this *_logger) SetRollingFileLoop(fileDir, fileName string, maxFileSize int64, unit _UNIT, maxFileNum int) (err error) {
  245. if fileDir == "" {
  246. fileDir, _ = os.Getwd()
  247. }
  248. if maxFileNum > 0 {
  249. maxFileNum--
  250. }
  251. this._fileDir, this._fileName, this._maxSize, this._maxFileNum, this._unit = fileDir, fileName, maxFileSize, maxFileNum, unit
  252. this._rolltype = _ROLLFILE
  253. if this._fileObj != nil {
  254. this._fileObj.close()
  255. }
  256. this.newfileObj()
  257. err = this._fileObj.openFileHandler()
  258. return
  259. }
  260. /*按日期分割日志文件
  261. fileDir 日志文件夹路径
  262. fileName 日志文件名
  263. */
  264. func (this *_logger) SetRollingDaily(fileDir, fileName string) (err error) {
  265. return this.SetRollingByTime(fileDir, fileName, MODE_DAY)
  266. }
  267. /*指定按 小时,天,月 分割日志文件
  268. fileDir 日志文件夹路径
  269. fileName 日志文件名
  270. mode 指定 小时,天,月
  271. */
  272. func (this *_logger) SetRollingByTime(fileDir, fileName string, mode _MODE_TIME) (err error) {
  273. if fileDir == "" {
  274. fileDir, _ = os.Getwd()
  275. }
  276. this._fileDir, this._fileName, this._mode = fileDir, fileName, mode
  277. this._rolltype = _DAYLY
  278. if this._fileObj != nil {
  279. this._fileObj.close()
  280. }
  281. this.newfileObj()
  282. err = this._fileObj.openFileHandler()
  283. return
  284. }
  285. func (this *_logger) newfileObj() {
  286. this._fileObj = new(fileObj)
  287. this._fileObj._fileDir, this._fileObj._fileName, this._fileObj._maxSize, this._fileObj._rolltype, this._fileObj._unit, this._fileObj._maxFileNum, this._fileObj._mode = this._fileDir, this._fileName, this._maxSize, this._rolltype, this._unit, this._maxFileNum, this._mode
  288. }
  289. func (this *_logger) backUp() (err, openFileErr error) {
  290. this._rwLock.Lock()
  291. defer this._rwLock.Unlock()
  292. if !this._fileObj.isMustBackUp() {
  293. return
  294. }
  295. err = this._fileObj.close()
  296. if err != nil {
  297. __print(this._format, LEVEL_ERROR, LEVEL_ERROR, 1, err.Error())
  298. return
  299. }
  300. err = this._fileObj.rename()
  301. if err != nil {
  302. __print(this._format, LEVEL_ERROR, LEVEL_ERROR, 1, err.Error())
  303. return
  304. }
  305. openFileErr = this._fileObj.openFileHandler()
  306. if openFileErr != nil {
  307. __print(this._format, LEVEL_ERROR, LEVEL_ERROR, 1, openFileErr.Error())
  308. }
  309. return
  310. }
  311. func (this *_logger) println(_level _LEVEL, calldepth int, v ...interface{}) {
  312. if this._level > _level {
  313. return
  314. }
  315. if this._fileObj._isFileWell {
  316. var openFileErr error
  317. if this._fileObj.isMustBackUp() {
  318. _, openFileErr = this.backUp()
  319. }
  320. if openFileErr == nil {
  321. func() {
  322. this._rwLock.RLock()
  323. defer this._rwLock.RUnlock()
  324. buf := getOutBuffer(formatV(v...), getlevelname(_level, this._format), this._format, k1(calldepth)+1)
  325. this._fileObj.write2file(buf.Bytes())
  326. }()
  327. }
  328. }
  329. if this._isConsole {
  330. __print(this._format, _level, this._level, k1(calldepth), v...)
  331. }
  332. }
  333. /*————————————————————————————————————————————————————————————————————————————*/
  334. type fileObj struct {
  335. _fileDir string
  336. _fileName string
  337. _maxSize int64
  338. _fileSize int64
  339. _unit _UNIT
  340. _fileHandler *os.File
  341. _rolltype _ROLLTYPE
  342. _tomorSecond int64
  343. _isFileWell bool
  344. _maxFileNum int
  345. _mode _MODE_TIME
  346. }
  347. func (this *fileObj) openFileHandler() (e error) {
  348. if this._fileDir == "" || this._fileName == "" {
  349. e = errors.New("log filePath is null or error")
  350. return
  351. }
  352. e = mkdirDir(this._fileDir)
  353. if e != nil {
  354. this._isFileWell = false
  355. return
  356. }
  357. fname := fmt.Sprint(this._fileDir, "/", this._fileName)
  358. this._fileHandler, e = os.OpenFile(fname, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
  359. if e != nil {
  360. __print(default_format, LEVEL_ERROR, LEVEL_ERROR, 1, e.Error())
  361. this._isFileWell = false
  362. return
  363. }
  364. this._isFileWell = true
  365. this._tomorSecond = tomorSecond(this._mode)
  366. fs, err := this._fileHandler.Stat()
  367. if err == nil {
  368. this._fileSize = fs.Size()
  369. } else {
  370. e = err
  371. }
  372. return
  373. }
  374. func (this *fileObj) addFileSize(size int64) {
  375. atomic.AddInt64(&this._fileSize, size)
  376. }
  377. func (this *fileObj) write2file(bs []byte) (e error) {
  378. defer catchError()
  379. if bs != nil {
  380. this.addFileSize(int64(len(bs)))
  381. _write2file(this._fileHandler, bs)
  382. }
  383. return
  384. }
  385. func (this *fileObj) isMustBackUp() bool {
  386. switch this._rolltype {
  387. case _DAYLY:
  388. if time.Now().Unix() >= this._tomorSecond {
  389. return true
  390. }
  391. case _ROLLFILE:
  392. return this._fileSize > 0 && this._fileSize >= this._maxSize*int64(this._unit)
  393. }
  394. return false
  395. }
  396. func (this *fileObj) rename() (err error) {
  397. bckupfilename := ""
  398. if this._rolltype == _DAYLY {
  399. bckupfilename = getBackupDayliFileName(this._fileDir, this._fileName, this._mode)
  400. } else {
  401. bckupfilename, err = getBackupRollFileName(this._fileDir, this._fileName)
  402. }
  403. if bckupfilename != "" && err == nil {
  404. oldPath := fmt.Sprint(this._fileDir, "/", this._fileName)
  405. newPath := fmt.Sprint(this._fileDir, "/", bckupfilename)
  406. err = os.Rename(oldPath, newPath)
  407. if err == nil && this._rolltype == _ROLLFILE && this._maxFileNum > 0 {
  408. go _rmOverCountFile(this._fileDir, bckupfilename, this._maxFileNum)
  409. }
  410. }
  411. return
  412. }
  413. func (this *fileObj) close() (err error) {
  414. defer catchError()
  415. if this._fileHandler != nil {
  416. err = this._fileHandler.Close()
  417. }
  418. return
  419. }
  420. func tomorSecond(mode _MODE_TIME) int64 {
  421. now := time.Now()
  422. switch mode {
  423. case MODE_DAY:
  424. return time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location()).Unix()
  425. case MODE_HOUR:
  426. return time.Date(now.Year(), now.Month(), now.Day(), now.Hour()+1, 0, 0, 0, now.Location()).Unix()
  427. case MODE_MONTH:
  428. return time.Date(now.Year(), now.Month()+1, 0, 0, 0, 0, 0, now.Location()).AddDate(0, 0, 1).Unix()
  429. default:
  430. return time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location()).Unix()
  431. }
  432. }
  433. func _yestStr(mode _MODE_TIME) string {
  434. now := time.Now()
  435. switch mode {
  436. case MODE_DAY:
  437. return now.AddDate(0, 0, -1).Format(_DATEFORMAT_DAY)
  438. case MODE_HOUR:
  439. return now.Add(-1 * time.Hour).Format(_DATEFORMAT_HOUR)
  440. case MODE_MONTH:
  441. return now.AddDate(0, -1, 0).Format(_DATEFORMAT_MONTH)
  442. default:
  443. return now.AddDate(0, 0, -1).Format(_DATEFORMAT_DAY)
  444. }
  445. }
  446. /*————————————————————————————————————————————————————————————————————————————*/
  447. func getBackupDayliFileName(dir, filename string, mode _MODE_TIME) (bckupfilename string) {
  448. timeStr := _yestStr(mode)
  449. index := strings.LastIndex(filename, ".")
  450. if index <= 0 {
  451. index = len(filename)
  452. }
  453. fname := filename[:index]
  454. suffix := filename[index:]
  455. bckupfilename = fmt.Sprint(fname, "_", timeStr, suffix)
  456. if isFileExist(fmt.Sprint(dir, "/", bckupfilename)) {
  457. bckupfilename = _getBackupfilename(1, dir, fmt.Sprint(fname, "_", timeStr), suffix)
  458. }
  459. return
  460. }
  461. func _getDirList(dir string) ([]os.DirEntry, error) {
  462. f, err := os.Open(dir)
  463. if err != nil {
  464. return nil, err
  465. }
  466. defer f.Close()
  467. return f.ReadDir(-1)
  468. }
  469. func getBackupRollFileName(dir, filename string) (bckupfilename string, er error) {
  470. list, err := _getDirList(dir)
  471. if err != nil {
  472. er = err
  473. return
  474. }
  475. index := strings.LastIndex(filename, ".")
  476. if index <= 0 {
  477. index = len(filename)
  478. }
  479. fname := filename[:index]
  480. suffix := filename[index:]
  481. length := len(list)
  482. bckupfilename = _getBackupfilename(length, dir, fname, suffix)
  483. return
  484. }
  485. func _getBackupfilename(count int, dir, filename, suffix string) (bckupfilename string) {
  486. bckupfilename = fmt.Sprint(filename, "_", count, suffix)
  487. if isFileExist(fmt.Sprint(dir, "/", bckupfilename)) {
  488. return _getBackupfilename(count+1, dir, filename, suffix)
  489. }
  490. return
  491. }
  492. func _write2file(f *os.File, bs []byte) (e error) {
  493. _, e = f.Write(bs)
  494. return
  495. }
  496. func _console(s string, levelname string, flag _FORMAT, calldepth int) {
  497. buf := getOutBuffer(s, levelname, flag, k1(calldepth))
  498. fmt.Print(&buf)
  499. }
  500. func outwriter(out io.Writer, prefix string, flag _FORMAT, calldepth int, s string) {
  501. l := log.New(out, prefix, int(flag))
  502. l.Output(k1(calldepth), s)
  503. }
  504. func k1(calldepth int) int {
  505. return calldepth + 1
  506. }
  507. func getOutBuffer(s string, levelname string, flag _FORMAT, calldepth int) (buf bytes.Buffer) {
  508. outwriter(&buf, levelname, flag, k1(calldepth), s)
  509. return
  510. }
  511. func mkdirDir(dir string) (e error) {
  512. _, er := os.Stat(dir)
  513. b := er == nil || os.IsExist(er)
  514. if !b {
  515. if err := os.MkdirAll(dir, 0666); err != nil {
  516. if os.IsPermission(err) {
  517. e = err
  518. }
  519. }
  520. }
  521. return
  522. }
  523. func isFileExist(path string) bool {
  524. _, err := os.Stat(path)
  525. return err == nil || os.IsExist(err)
  526. }
  527. func catchError() {
  528. if err := recover(); err != nil {
  529. Fatal(string(debug.Stack()))
  530. }
  531. }
  532. func _rmOverCountFile(dir, backupfileName string, maxFileNum int) {
  533. static_mu.Lock()
  534. defer static_mu.Unlock()
  535. f, err := os.Open(dir)
  536. if err != nil {
  537. return
  538. }
  539. dirs, _ := f.ReadDir(-1)
  540. f.Close()
  541. if len(dirs) <= maxFileNum {
  542. return
  543. }
  544. sort.Slice(dirs, func(i, j int) bool {
  545. f1, _ := dirs[i].Info()
  546. f2, _ := dirs[j].Info()
  547. return f1.ModTime().Unix() > f2.ModTime().Unix()
  548. })
  549. index := strings.LastIndex(backupfileName, "_")
  550. indexSuffix := strings.LastIndex(backupfileName, ".")
  551. if indexSuffix == 0 {
  552. indexSuffix = len(backupfileName)
  553. }
  554. prefixname := backupfileName[:index+1]
  555. suffix := backupfileName[indexSuffix:]
  556. suffixlen := len(suffix)
  557. rmfiles := make([]string, 0)
  558. i := 0
  559. for _, f := range dirs {
  560. if len(f.Name()) > len(prefixname) && f.Name()[:len(prefixname)] == prefixname && _matchString("^[0-9]+$", f.Name()[len(prefixname):len(f.Name())-suffixlen]) {
  561. finfo, err := f.Info()
  562. if err == nil && !finfo.IsDir() {
  563. i++
  564. if i > maxFileNum {
  565. rmfiles = append(rmfiles, fmt.Sprint(dir, "/", f.Name()))
  566. }
  567. }
  568. }
  569. }
  570. if len(rmfiles) > 0 {
  571. for _, k := range rmfiles {
  572. os.Remove(k)
  573. }
  574. }
  575. }
  576. func _matchString(pattern string, s string) bool {
  577. b, err := regexp.MatchString(pattern, s)
  578. if err != nil {
  579. b = false
  580. }
  581. return b
  582. }
  583. func formatV(v ...interface{}) string {
  584. s := strings.Builder{}
  585. for _, vv := range v {
  586. if s.Len() > 0 {
  587. s.WriteString(" ")
  588. }
  589. s.WriteString(fmt.Sprint(vv))
  590. }
  591. return s.String()
  592. }