-
Notifications
You must be signed in to change notification settings - Fork 20
/
tmlog.go
929 lines (781 loc) · 28.4 KB
/
tmlog.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
/**
* @file: tmlog.go
* @version: v1.0.0
* @package: tmlog
* @author: heiyeluren
* @desc: Log operate file
* @date: 2013/6/24
* @history:
* 2013/6/24 created file
* 2013/7/01 add logid function
* 2013/7/02 update code structure
* 2013/7/04 refactor all code
* 2013/7/10 add log_level operate
* 2013/8/03 update comment
* 2013/8/15 update Log_Run() code, modified call Log_Run operating mode
* 2020/10 update document and select{}
* 2022/2 update document and test code
*
* @feature list
* 1. 基本架构:
* a. 采用 日志输入(客户端) -> 日志输出(服务器端) 的架构,日志输入可以任意调用,日志输出是一个单独协程在工作,
* b. 能够保证日志保证时序性,并且保证客户端可以无限量的写入日志,不用担心阻塞而影响性能.
* c. 应用场景: 适合高性能日志打印的场合,按照测试,能够在每秒1万次请求的后端服务上进行日志打印,不会对性能有太多影响
*
* 2. 主要功能:
* a. 日志类型: 可以打印5种类型(notice/trace/debug/warning/fatal)的日志,并且代码很容易新增类型,不过基本够用了,
* 同时可以配置里定制那些日志类型需要记录, 一般推荐最少最少记录 notice/warning/fatal 三种日志
* b. 日志文件: 不同日志类型可以单独输出到指定日志文件中, 一般建议 notice/trace/debug 放一个日志文件, warning/fatal 放一个日志文件
* c. 日志切割: 支持按照 天/小时/10分钟 三种粒度自动进行日志文件切割,方便控制日志文件大小
* d. 日志刷盘: 可以指定日志刷盘的时间, 缺省1秒,建议不超过3秒; 如果当前日志达到缓存90%占用,会自动刷盘,保证不会阻塞写日志操作
* e. 调试模式: 支持调试模式,可以在运行中在终端输出一些信息,方便监测
*
* @other
* 博客: http://blog.csdn.net/heiyeshuwu
* 微博: http://weibo.com/heiyeluren
* 公众号: heiyeluren2012
* Github: github.com/heiyeluren
*
*/
package tmlog
import (
"errors"
"fmt"
"math/rand"
"os"
"runtime"
"strconv"
"strings"
"sync"
"time"
)
/*
日志配置文件格式:(可以使用配置文件,或者直接在调用的时候传递map给tmlog启动协程)
=================================================
#--------------
#日志操作配置
#--------------
#日志文件位置, 可以配置不同的日志消息类型到不同的日志文件 (例:/var/log/heiyeluren.log)
log_notice_file_path = log/heiyeluren.log
log_debug_file_path = log/heiyeluren.log
log_trace_file_path = log/heiyeluren.log
log_fatal_file_path = log/heiyeluren.log.wf
log_warning_file_path = log/heiyeluren.log.wf
#日志文件切割周期(1天:day; 1小时:hour; 10分钟:ten)
log_cron_time = day
#日志chan队列的buffer长度,在高并发服务器下,建议不要少于10240, 越大越好
#不建议多于1024000,测试最长: 67021478 (超过这个值会无法启动)
log_chan_buff_size = 1024000
#日志刷盘的间隔时间,单位:毫秒,建议500~5000毫秒(0.5s-5s),建议不超过30秒
log_flush_timer = 1000
#是否开启日志库调试模式(会在终端打印一些日志, 1:开启, 0:关闭)
log_debug_open = 0
#输出日志的级别 (fatal:1,warngin:2,notice:4,trace:8,debug:16)
#级别描述主要是确定需要打印什么级别的日志,数字配置是一个需要打印日志级别数字的"或"操作总数(简单理解为加)
#如果只打印错误警告和notice日志则是7,如果需要打印所有日志则是31,如果只需打印除trace以外的日志,则是23
#如果不想输出任何日志,可以设置为0,特别在性能测试的时候,屏蔽刷日志带来的影响
log_level = 31
=================================================
================= 启动日志工作协程代码 =================
import "heiyeluren/tmlog"
// 传递给日志类的配置项
// 说明: 这些配置实际可以放置到配置文件里,不过我们这里只是演示,直接就生成map了
logConf := map[string]string{
"log_notice_file_path": "log/heiyeluren.log",
"log_debug_file_path": "log/heiyeluren.log",
"log_trace_file_path": "log/heiyeluren.log",
"log_fatal_file_path": "log/heiyeluren.log.wf",
"log_warning_file_path": "log/heiyeluren.log.wf",
"log_cron_time": "day",
"log_chan_buff_size": "1024",
"log_flush_timer": "1000",
"log_debug_open": "1",
"log_level": "31",
}
// 启动 tmlog 工作协程, 可以理解为tmlog的服务器端
tmlog.Log_Run(logConf)
================= 打印日志代码调用示例 =================
//在main主协程里打印日志, 可以理解为tmlog的客户端往服务器端输入日志
//说明: 这些代码可以在单独的任何非 tmlog 协程之外工作, 包括main协程, 或者是某些业务处理协程
//打印日志1: 由tmlog生成logid, 生成log操作句柄, 打印notice和warning两条日志
logHandle1 := tmlog.NewLogger("")
logHandle1.Notice("[logger=logHandle1 msg='The notice message is test']")
logHandle1.Warning("[logger=logHandle1 msg='The warning message is test']")
//打印日志2: 调用方指定logid, 生成另外log操作句柄, 打印notice和warning两条日志
//注意: 这里只是演示,为了追查问题方便, 一般情况不建议一个请求使用多个logid
logHandle2 := tmlog.NewLogger("123456789")
logHandle2.Notice("[logger=logHandle2 msg='The notice message is test']")
logHandle2.Warning("[logger=logHandle2 msg='The warning message is test']")
//打印日志3: 在一个单独协程里打印日志
go LogTest()
func LogTest() {
logHandle3 := tmlog.NewLogger("987654321")
logHandle3.Notice("[logger=logHandle3 msg='The notice message is test']")
logHandle3.Warning("[logger=logHandle3 msg='The warning message is test']")
}
*/
//========================
//
// 外部调用Logger方法
//
//========================
/**
* Log 每次请求结构体数据
*/
type Logger struct {
Logid string
}
//日志级别类型常量
const (
LOG_TYPE_FATAL = 1
LOG_TYPE_WARNING = 2
LOG_TYPE_NOTICE = 4
LOG_TYPE_TRACE = 8
LOG_TYPE_DEBUG = 16
)
//日志类型对应信息
const (
LOG_TYPE_FATAL_STR = "FATAL"
LOG_TYPE_WARNING_STR = "WARNING"
LOG_TYPE_NOTICE_STR = "NOTICE"
LOG_TYPE_TRACE_STR = "TRACE"
LOG_TYPE_DEBUG_STR = "DEBUG"
)
//日志信息map
var G_Log_Type_Map map[int]string = map[int]string{
LOG_TYPE_FATAL: LOG_TYPE_FATAL_STR,
LOG_TYPE_WARNING: LOG_TYPE_WARNING_STR,
LOG_TYPE_NOTICE: LOG_TYPE_NOTICE_STR,
LOG_TYPE_TRACE: LOG_TYPE_TRACE_STR,
LOG_TYPE_DEBUG: LOG_TYPE_DEBUG_STR,
}
//------------------------
// logger外部调用方法
//------------------------
/**
* 构造函数
*
*/
func NewLogger(logid string) *Logger {
return &Logger{Logid: logid}
}
/**
* 正常请求日志打印调用
*
* 注意:
* 每个请求(request)只能调用本函数一次,函数里必须携带必须字段: ip, errno, errmsg 等字段,其他kv信息自己组织
*
* 示例:
* Log_Notice("clientip=192.168.0.1 errno=0 errmsg=ok key1=valu2 key2=valu2")
*
*/
func (l *Logger) Notice(log_messgae string) {
l.sync_msg(LOG_TYPE_NOTICE, log_messgae)
}
/**
* 函数调用栈trace日志打印调用
*
*/
func (l *Logger) Trace(log_messgae string) {
l.sync_msg(LOG_TYPE_TRACE, log_messgae)
}
/**
* 函数调用调试debug日志打印调用
*
*/
func (l *Logger) Debug(log_messgae string) {
l.sync_msg(LOG_TYPE_DEBUG, log_messgae)
}
/**
* 致命错误Fatal日志打印调用
*
*/
func (l *Logger) Fatal(log_messgae string) {
l.sync_msg(LOG_TYPE_FATAL, log_messgae)
}
/**
* 警告错误warging日志打印调用
*
*/
func (l *Logger) Warning(log_messgae string) {
l.sync_msg(LOG_TYPE_WARNING, log_messgae)
}
//------------------------
// logger内部使用方法
//------------------------
/**
* 写入日志到channel
*
*/
func (l *Logger) sync_msg(log_type int, log_msg string) error {
//init request log
//Log_New()
//time.Sleep(time.Second * 1)
// println("In sync_msg")
Log_Debug_Print(fmt.Sprintf("Log Message log_type=%v log_msg=%v", log_type, log_msg), nil)
//从配置日志级别log_level判断当前日志是否需要入channel队列
if (log_type & G_Log_V.LogLevel) != log_type {
return nil
}
//G_Log_V := Log_New(G_Log_V)
if log_type <= 0 || log_msg == "" {
errors.New("log_type or log_msg param is empty")
}
//拼装消息内容
log_str := l.pad_msg(log_type, log_msg)
//日志类型
if _, ok := G_Log_Type_Map[log_type]; !ok {
errors.New("log_type is invalid")
}
//设定消息格式
log_msg_data := Log_Msg_T{
LogType: log_type,
LogData: log_str,
}
//写消息到channel
G_Log_V.LogChan <- log_msg_data
//判断当前整个channel 的buffer大小是否超过90%的阀值,超过就直接发送刷盘信号
var threshold float32
var curr_chan_len int = len(G_Log_V.LogChan)
threshold = float32(curr_chan_len) / float32(G_Log_V.LogChanBuffSize)
if threshold >= 0.9 && G_Flush_Log_Flag != true {
G_Flush_Lock.Lock()
G_Flush_Log_Flag = true
G_Flush_Lock.Unlock()
G_Log_V.FlushLogChan <- true
//打印目前达到阀值了
if Log_Is_Debug() {
Log_Debug_Print(fmt.Sprintf("Out threshold!! Current G_Log_V.LogChan: %v; G_Log_V.LogChanBuffSize: %v", curr_chan_len, G_Log_V.LogChanBuffSize), nil)
}
}
return nil
}
/**
* 拼装日志消息
*
* 说明:
* 主要是按照格式把消息给拼装起来
*
* 日志格式示例:
* NOTICE: 2013-06-28 18:30:56 heiyeluren [logid=1234 filename=yyy.go lineno=29] [clientip=10.5.0.108 errno=0 errmsg="ok"]
* WARNING: 2013-06-28 18:30:56 heiyeluren [logid=1234 filename=yyy.go lineno=29] [clientip=10.5.0.108 errno=404 errmsg="json format invalid"]
*/
func (l *Logger) pad_msg(log_type int, log_msg string) string {
var (
//日志拼装格式字符串
log_format_str string
log_ret_str string
//日志所需字段变量
log_type_str string
log_date_time string
log_id string
log_filename string
log_lineno int
log_callfunc string
//log_clientip string
//log_errno int
//log_errmsg string
//其他变量
ok bool
fcName uintptr
)
//获取调用的 函数/文件名/行号 等信息
fcName, log_filename, log_lineno, ok = runtime.Caller(3)
if !ok {
errors.New("call runtime.Caller() fail")
}
log_callfunc = runtime.FuncForPC(fcName).Name()
//展现调用文件名最后两段
//println(log_filename)
//判断当前操作系统路径分割符,获取调用文件最后两组路径信息
os_path_separator := Log_Get_Os_Separator(log_filename)
call_path := strings.Split(log_filename, os_path_separator)
if path_len := len(call_path); path_len > 2 {
log_filename = strings.Join(call_path[path_len-2:], os_path_separator)
}
//获取当前日期时间 (#吐槽: 不带这么奇葩的调用参数好不啦!难道这天是Go诞生滴日子??!!!#)
log_date_time = time.Now().Format("2006-01-02 15:04:05")
//app name
//log_app_name = "heiyeluren"
//logid读取
log_id = l.get_logid()
//日志类型
if log_type_str, ok = G_Log_Type_Map[log_type]; !ok {
errors.New("log_type is invalid")
}
//拼装返回
log_format_str = "%s: %s [logid=%s file=%s no=%d call=%s] %s\n"
log_ret_str = fmt.Sprintf(log_format_str, log_type_str, log_date_time, log_id, log_filename, log_lineno, log_callfunc, log_msg)
//调试
//println(log_ret_str)
return log_ret_str
}
/**
* 获取LogID
*
* 说明:
* 从客户端request http头里看看是否可以获得logid,http头里可以传递一个:WD_REQUEST_ID
* 如果没有传递,则自己生成唯一logid
*/
func (l *Logger) get_logid() string {
//获取request http头中的logid字段
if l.Logid != "" {
return l.Logid
}
return l.gen_logid()
}
/**
* 生成当前请求的Log ID
*
* 策略:
* 主要是保证唯一logid,采用当前纳秒级时间+随机数生成
*/
func (l *Logger) gen_logid() string {
//获取当前时间
microtime := time.Now().UnixNano()
//生成随机数
r := rand.New(rand.NewSource(microtime))
randNum := r.Intn(100000)
//生成logid:把纳秒时间+随机数生成 (注意:int64的转string使用 FormatInt,int型使用Itoa就行了)
//logid := fmt.Sprintf("%d%d", microtime, randNum)
logid := strconv.FormatInt(microtime, 10) + strconv.Itoa(randNum)
return logid
}
//========================
//
// 内部协程Run函数
//
//========================
/**
* 单条日志结构
*/
type Log_Msg_T struct {
LogType int
LogData string
}
/**
* Log主chan队列配置
*/
type Log_T struct {
//------------------
// Channel数据
//------------------
//日志接收channel队列
LogChan chan Log_Msg_T
//是否马上日志刷盘: true or false,如果为true,则马上日志刷盘 (本chan暂时没有使用)
FlushLogChan chan bool
//------------------
// 配置相关数据
//------------------
//所有日志文件位置
LogFilePath map[int]string
//日志文件位置 (例:/var/log/heiyeluren.log 和 /var/log/heiyeluren.log.wf)
LogNoticeFilePath string
LogErrorFilePath string
//写入日志切割周期(1天:day、1小时:hour、15分钟:Fifteen、10分钟:Ten)
LogCronTime string
//日志chan队列的buffer长度,建议不要少于1024,不多于102400,最长:2147483648
LogChanBuffSize int
//按照间隔时间日志刷盘的日志的间隔时间,单位:秒,建议1~5秒,不超过256
LogFlushTimer int
//------------------
// 运行时相关数据
//------------------
//去重的日志文件名和fd (实际需需要物理写入文件名和句柄)
MergeLogFile map[string]string
MergeLogFd map[string]*os.File
//上游配置的map数据(必须包含所有所需项)
RunConfigMap map[string]string
//是否开启日志库调试模式
LogDebugOpen bool
//日志打印的级别(需要打印那些日志)
LogLevel int
}
/**
* 配置项相关常量&变量
*/
const (
LOG_CONF_NOTICE_FILE_PATH = "log_notice_file_path"
LOG_CONF_DEBUG_FILE_PATH = "log_debug_file_path"
LOG_CONF_TRACE_FILE_PATH = "log_trace_file_path"
LOG_CONF_FATAL_FILE_PATH = "log_fatal_file_path"
LOG_CONF_WARNING_FILE_PATH = "log_warning_file_path"
LOG_CONF_CRON_TIME = "log_cron_time"
LOG_CONF_CHAN_BUFFSIZE = "log_chan_buff_size"
LOG_CONF_FLUSH_TIMER = "log_flush_timer"
LOG_CONF_DEBUG_OPEN = "log_debug_open"
LOG_CONF_LEVEL = "log_level"
)
//配置选项值类型(字符串或数字)
const (
LOG_CONF_TYPE_STR = 1
LOG_CONF_TYPE_NUM = 2
)
//配置项map全局变量 (定义一个选项输入的值是字符串还是数字)
var G_Conf_Item_Map map[string]int = map[string]int{
LOG_CONF_NOTICE_FILE_PATH: LOG_CONF_TYPE_STR,
LOG_CONF_DEBUG_FILE_PATH: LOG_CONF_TYPE_STR,
LOG_CONF_TRACE_FILE_PATH: LOG_CONF_TYPE_STR,
LOG_CONF_FATAL_FILE_PATH: LOG_CONF_TYPE_STR,
LOG_CONF_WARNING_FILE_PATH: LOG_CONF_TYPE_STR,
LOG_CONF_CRON_TIME: LOG_CONF_TYPE_STR,
LOG_CONF_CHAN_BUFFSIZE: LOG_CONF_TYPE_NUM,
LOG_CONF_FLUSH_TIMER: LOG_CONF_TYPE_NUM,
LOG_CONF_DEBUG_OPEN: LOG_CONF_TYPE_NUM,
LOG_CONF_LEVEL: LOG_CONF_TYPE_NUM,
}
//日志文件名与日志类型的映射
var G_Conf_FileToType_Map map[string]int = map[string]int{
LOG_CONF_NOTICE_FILE_PATH: LOG_TYPE_NOTICE,
LOG_CONF_DEBUG_FILE_PATH: LOG_TYPE_DEBUG,
LOG_CONF_TRACE_FILE_PATH: LOG_TYPE_TRACE,
LOG_CONF_FATAL_FILE_PATH: LOG_TYPE_FATAL,
LOG_CONF_WARNING_FILE_PATH: LOG_TYPE_WARNING,
}
//日志全局变量
var G_Log_V *Log_T
//全局once
var G_Once_V sync.Once
//目前是否已经写入刷盘操作channel(保证全局只能写入一次,防止多协程操作阻塞)
var G_Flush_Log_Flag bool = false
//控制 G_Flush_Log_Flag 的全局锁
var G_Flush_Lock *sync.Mutex = &sync.Mutex{}
/**
* 提供给协程调用的入口函数
*
* @param RunConfigMap 是需要传递进来的配置信息key=>val的map数据
* 调用示例:
*
//注意本调用必须在单独协程里运行
logConf := map[string]string{
"log_notice_file_path": "log/heiyeluren.log",
"log_debug_file_path": "log/heiyeluren.log",
"log_trace_file_path": "log/heiyeluren.log",
"log_fatal_file_path": "log/heiyeluren.log.wf",
"log_warning_file_path": "log/heiyeluren.log.wf",
"log_cron_time": "day",
"log_chan_buff_size": "1024",
"log_flush_timer": "1000",
"log_debug_open": "1",
"log_level": "31",
}
// 启动 tmlog 工作协程, 可以理解为tmlog的服务器端
tmlog.Log_Run(logConf)
* 注意:
* 需要传递进来的配置是有要求的,必须是包含这些配置选项,否则会报错
*
*/
func Log_Run(RunConfigMap map[string]string) {
//初始化全局变量
if G_Log_V == nil {
G_Log_V = new(Log_T)
}
//设置配置map数据
G_Log_V.RunConfigMap = RunConfigMap
//fmt.Println(G_Log_V)
//调用初始化操作,全局只运行一次
G_Once_V.Do(Log_Init)
//内部日志工作协程
go func() {
//永远循环等待channel的日志数据
var log_msg Log_Msg_T
//var num int64
for {
//监控是否有可以日志可以存取
select {
case log_msg = <-G_Log_V.LogChan:
if Log_Is_Debug() {
Log_Debug_Print("In select{ log_msg = <-G_Log_V.LogChan, Log_Write_File() } G_Log_V.LogChan Length:", len(G_Log_V.LogChan))
}
Log_Write_File(log_msg)
//if Log_Is_Debug() {
// Log_Debug_Print("G_Log_V.LogChan Length:", len(G_Log_V.LogChan))
//}
default:
//breakLogChan长度
//println("In Default ", num)
//打印目前G_Log_V的数据
if Log_Is_Debug() {
Log_Debug_Print("In select{ default }, G_Log_V.LogChan Length:", len(G_Log_V.LogChan))
}
time.Sleep(time.Duration(G_Log_V.LogFlushTimer) * time.Millisecond)
}
//监控刷盘timer
//log_timer := time.NewTimer(time.Duration(G_Log_V.LogFlushTimer) * time.Millisecond)
select {
//超过设定时间开始检测刷盘(保证不会频繁写日志操作)
//case <-log_timer.C:
// log_timer.Stop()
// break
//如果收到刷盘channel的信号则刷盘且全局标志状态为
case <-G_Log_V.FlushLogChan:
if Log_Is_Debug() {
Log_Debug_Print("In select{ G_Flush_Log_Flag }, G_Log_V.LogChan Length:", len(G_Log_V.LogChan))
}
G_Flush_Lock.Lock()
G_Flush_Log_Flag = false
G_Flush_Lock.Unlock()
//log_timer.Stop()
break
default:
break
}
}
}()
}
/**
* 初始化Log协程相关操作
*
* 注意:
* 全局操作, 只能协程初始化的时候调用一次
*
*/
func Log_Init() {
if G_Log_V.RunConfigMap == nil {
errors.New("Log_Init fail: RunConfigMap data is nil")
}
//println("in Log_Init")
//构建日志文件名和文件句柄map内存
G_Log_V.LogFilePath = make(map[int]string, len(G_Log_Type_Map))
//判断各个配置选项是否存在
for conf_item_key, _ := range G_Conf_Item_Map {
if _, ok := G_Log_V.RunConfigMap[conf_item_key]; !ok {
errors.New(fmt.Sprintf("Log_Init fail: RunConfigMap not include item: %s", conf_item_key))
}
}
//扫描所有配置选项赋值给结构体
var err error
var item_val_str string
var item_val_num int
for conf_item_k, conf_item_v := range G_Conf_Item_Map {
//对所有配置选项 进行类型转换
if conf_item_v == LOG_CONF_TYPE_STR {
item_val_str = string(G_Log_V.RunConfigMap[conf_item_k])
} else if conf_item_v == LOG_CONF_TYPE_NUM {
if item_val_num, err = strconv.Atoi(G_Log_V.RunConfigMap[conf_item_k]); err != nil {
errors.New(fmt.Sprintf("Log conf read map[%s] fail, map is error", conf_item_k))
}
}
//进行各选项赋值
switch conf_item_k {
//日志文件路径
case LOG_CONF_NOTICE_FILE_PATH:
G_Log_V.LogFilePath[LOG_TYPE_NOTICE] = item_val_str
case LOG_CONF_DEBUG_FILE_PATH:
G_Log_V.LogFilePath[LOG_TYPE_DEBUG] = item_val_str
case LOG_CONF_TRACE_FILE_PATH:
G_Log_V.LogFilePath[LOG_TYPE_TRACE] = item_val_str
case LOG_CONF_FATAL_FILE_PATH:
G_Log_V.LogFilePath[LOG_TYPE_FATAL] = item_val_str
case LOG_CONF_WARNING_FILE_PATH:
G_Log_V.LogFilePath[LOG_TYPE_WARNING] = item_val_str
//其他配置选项
case LOG_CONF_CRON_TIME:
G_Log_V.LogCronTime = item_val_str
case LOG_CONF_CHAN_BUFFSIZE:
G_Log_V.LogChanBuffSize = item_val_num
case LOG_CONF_FLUSH_TIMER:
G_Log_V.LogFlushTimer = item_val_num
case LOG_CONF_DEBUG_OPEN:
if item_val_num == 1 {
G_Log_V.LogDebugOpen = true
} else {
G_Log_V.LogDebugOpen = false
}
case LOG_CONF_LEVEL:
G_Log_V.LogLevel = item_val_num
}
}
//设置日志channel buffer
if G_Log_V.LogChanBuffSize <= 0 {
G_Log_V.LogChanBuffSize = 1024
}
G_Log_V.LogChan = make(chan Log_Msg_T, G_Log_V.LogChanBuffSize)
//初始化唯一的日志文件名和fd
G_Log_V.MergeLogFile = make(map[string]string, len(G_Log_Type_Map))
G_Log_V.MergeLogFd = make(map[string]*os.File, len(G_Log_Type_Map))
for _, log_file_path := range G_Log_V.LogFilePath {
G_Log_V.MergeLogFile[log_file_path] = ""
G_Log_V.MergeLogFd[log_file_path] = nil
}
//打印目前G_Log_V的数据
if Log_Is_Debug() {
Log_Debug_Print("[ G_Log_V data ]", G_Log_V)
}
}
/**
* 写日志操作
*
*/
func Log_Write_File(log_msg Log_Msg_T) {
//读取多少行开始写日志
//var max_line_num int
//临时变量
var (
//动态生成需要最终输出的日志map
log_map map[string][]string
//读取单条的日志消息
log_msg_var Log_Msg_T
//读取单个配置的日志文件名
conf_file_name string
write_buf string
line string
)
//打开文件
Log_Open_File()
//初始化map数据都为
log_map = make(map[string][]string, len(G_Conf_FileToType_Map))
for conf_file_name, _ = range G_Log_V.MergeLogFile {
log_map[conf_file_name] = []string{}
}
//fmt.Println(log_map)
//压入第一条读取的日志(上游select读取的)
conf_file_name = G_Log_V.LogFilePath[log_msg.LogType]
log_map[conf_file_name] = []string{log_msg.LogData}
//fmt.Println(log_map)
//读取日志(所有可读的日志都读取,然后按照需要打印的文件压入到不同map数组)
select {
case log_msg_var = <-G_Log_V.LogChan:
conf_file_name = G_Log_V.LogFilePath[log_msg_var.LogType]
log_map[conf_file_name] = append(log_map[conf_file_name], log_msg_var.LogData)
default:
break
}
//调试信息
if Log_Is_Debug() {
Log_Debug_Print("Log Map:", log_map)
}
//写入所有日志(所有map所有文件的都写)
for conf_file_name, _ = range G_Log_V.MergeLogFile {
if len(log_map[conf_file_name]) > 0 {
write_buf, line = "", ""
for _, line = range log_map[conf_file_name] {
write_buf += line
}
_, _ = G_Log_V.MergeLogFd[conf_file_name].WriteString(write_buf)
_ = G_Log_V.MergeLogFd[conf_file_name].Sync()
//调试信息
if Log_Is_Debug() {
Log_Debug_Print("Log String:", write_buf)
}
}
}
}
/**
* 打开&切割日志文件
*
*/
func Log_Open_File() error {
var (
file_suffix string
err error
conf_file_name string
run_file_name string
new_log_file_name string
new_log_file_fd *os.File
)
//构造日志文件名
file_suffix = Log_Get_File_Suffix()
//把重复日志文件都归一,然后进行相应日志文件的操作
for conf_file_name, run_file_name = range G_Log_V.MergeLogFile {
new_log_file_name = fmt.Sprintf("%s.%s", conf_file_name, file_suffix)
//如果新旧文件名不同,说明需要切割文件了(第一次运行则是全部初始化文件)
if new_log_file_name != run_file_name {
//关闭旧日志文件
if G_Log_V.MergeLogFd[conf_file_name] != nil {
if err = G_Log_V.MergeLogFd[conf_file_name].Close(); err != nil {
errors.New(fmt.Sprintf("Close log file %s fail", run_file_name))
}
}
//初始化新日志文件
G_Log_V.MergeLogFile[conf_file_name] = new_log_file_name
G_Log_V.MergeLogFd[conf_file_name] = nil
//创建&打开新日志文件
new_log_file_fd, err = os.OpenFile(new_log_file_name, os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
errors.New(fmt.Sprintf("Open log file %s fail", new_log_file_name))
}
new_log_file_fd.Seek(0, os.SEEK_END)
//把处理的相应的结果进行赋值
G_Log_V.MergeLogFile[conf_file_name] = new_log_file_name
G_Log_V.MergeLogFd[conf_file_name] = new_log_file_fd
}
}
//调试
//fmt.Println(G_Log_V)
return nil
}
/**
* 获取日志文件的切割时间
*
* 说明:
* 目前主要支持三种粒度的设置,基本这些粒度足够我们使用了
* 1天:day; 1小时:hour; 10分钟:ten
*/
func Log_Get_File_Suffix() string {
var file_suffix string
now := time.Now()
switch G_Log_V.LogCronTime {
//按照天切割日志
case "day":
file_suffix = now.Format("20060102")
//按照小时切割日志
case "hour":
file_suffix = now.Format("20060102_15")
//按照10分钟切割日志
case "ten":
file_suffix = fmt.Sprintf("%s%d0", now.Format("20060102_15"), int(now.Minute()/10))
//缺省按照小时
default:
file_suffix = now.Format("20060102_15")
}
return file_suffix
}
/**
* 获取目前是否是Debug模式
*
*/
func Log_Is_Debug() bool {
if G_Log_V.LogDebugOpen {
return true
}
return false
}
/**
* 日志打印输出到终端函数
*
*/
func Log_Debug_Print(msg string, v interface{}) {
//获取调用的 函数/文件名/行号 等信息
fcName, log_filename, log_lineno, ok := runtime.Caller(1)
if !ok {
errors.New("call runtime.Caller() fail")
}
log_callfunc := runtime.FuncForPC(fcName).Name()
os_path_separator := Log_Get_Os_Separator(log_filename)
call_path := strings.Split(log_filename, os_path_separator)
if path_len := len(call_path); path_len > 2 {
log_filename = strings.Join(call_path[path_len-2:], os_path_separator)
}
fmt.Println("\n=======================Log Debug Info Start=======================")
fmt.Printf("[ call=%v file=%v line=%v ]\n", log_callfunc, log_filename, log_lineno)
if msg != "" {
fmt.Println(msg)
}
fmt.Println(v)
fmt.Println("=======================Log Debug Info End=======================\n")
}
/**
* 获取当前操作系统的路径切割符
*
* 说明: 主要为了解决 os.PathSeparator有些时候无法满足要求的问题
*
*/
func Log_Get_Os_Separator(path_name string) string {
//判断当前操作系统路径分割符
var os_path_separator = "/"
if strings.ContainsAny(path_name, "\\") {
os_path_separator = "\\"
}
return os_path_separator
}