惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cloudbric
Cloudbric
Schneier on Security
Schneier on Security
V2EX - 技术
V2EX - 技术
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
O
OpenAI News
S
Security @ Cisco Blogs
Scott Helme
Scott Helme
Security Archives - TechRepublic
Security Archives - TechRepublic
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
WordPress大学
WordPress大学
云风的 BLOG
云风的 BLOG
T
Threatpost
Hacker News: Ask HN
Hacker News: Ask HN
Microsoft Azure Blog
Microsoft Azure Blog
Know Your Adversary
Know Your Adversary
博客园 - 三生石上(FineUI控件)
A
About on SuperTechFans
Forbes - Security
Forbes - Security
NISL@THU
NISL@THU
Security Latest
Security Latest
G
Google Developers Blog
D
Docker
T
Threat Research - Cisco Blogs
N
Netflix TechBlog - Medium
C
CERT Recently Published Vulnerability Notes
H
Help Net Security
B
Blog
Martin Fowler
Martin Fowler
N
News and Events Feed by Topic
Simon Willison's Weblog
Simon Willison's Weblog
Hacker News - Newest:
Hacker News - Newest: "LLM"
L
Lohrmann on Cybersecurity
Y
Y Combinator Blog
PCI Perspectives
PCI Perspectives
F
Fortinet All Blogs
MyScale Blog
MyScale Blog
Project Zero
Project Zero
爱范儿
爱范儿
Cisco Talos Blog
Cisco Talos Blog
博客园 - 聂微东
Hugging Face - Blog
Hugging Face - Blog
人人都是产品经理
人人都是产品经理
V
Vulnerabilities – Threatpost
P
Proofpoint News Feed
Cyberwarzone
Cyberwarzone
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
TaoSecurity Blog
TaoSecurity Blog
N
News | PayPal Newsroom
Recorded Future
Recorded Future

Posts on WKLKEN THINKING

apisix 中的 lrucache apisix 中的服务发现机制 apisix 中的负载均衡 apisix etcd机制 聊聊框架 关于 k8s 的 zero downtime deployment 一些建议 apisix 遇到的一些问题 关于在除夕前一天换了一个洗衣机的故事 Django DRF 性能优化 DRF 的一些实践 Part1: Serializer DRF继承关系图 Better Code: 关于接口的灵活性 新的仓库: wklken/naming 缓存使用的一些经验 Better Code: 抽象: 可扩展性与可维护性的抉择 Better Code: 异常时, 该提示用户哪些信息? Better Code: 更好的异常日志打印 Go: some libs Go: go-redis/cache升级的坑 Go: gin validation 远程办公的一点总结 Go: 开发过程中的一些bug 项目管理实践: 风险驱动开发 Go: 一种error wrap调用链处理方式 漫谈技术选型 Go: 基于 apitest 做handler层单元测试 Go: go-sql-driver interpolateparams参数优化 [分享]深度工作 你需要更多的思考时间 Django项目重构小结 工作七年小结: 学习,生活及其他 [分享]bash日常: bash-utils 极客时间推广海报 2017总结: 予时光以意义 k8s APIServer源码: api注册详细细节 k8s APIServer源码: api注册主体流程 k8s APIServer源码: 服务启动 k8s APIServer源码: go-restful框架 重构 - 读书笔记(Python示例) 写给新人的沟通建议 vim 杂谈 - 关于快速编辑 vim 杂谈 - 关于移动 读书笔记-重构: 章11 处理概括关系 读书笔记-重构: 章10 简化函数调用 读书笔记-重构: 章9 简化表达式 读书笔记-重构: 章8 重新组织数据 读书笔记-重构: 章7 在对象之间搬移特性 读书笔记-重构: 章6 重新组织函数 Python 代码规范小结 [分享]关于vim ElasticSearch集群部署文档 Logstash+ElasticSearch处理mysql慢查询日志 [分享]关于代码调试DE那些事 Logstash+ElasticSearch+Kibana- 实现相对通用的数据收集分析 ELK维护的一些点(二) [分享]Python源码剖析-数据结构 一些Centos Python生产环境的部署命令 摘录<<6个月学会任何一种外语>> ELK 维护的一些点 也许是一个新的开始 一些vim的个性化配置 读书笔记-调试九法 这段时间的一些想法 Python 源码阅读 - 垃圾回收机制 我为什么要写博客 APUE笔记-第一章 UNIX基础知识 Python源码阅读-闭包的实现 Python源码阅读-内存管理机制(二) Python源码阅读-内存管理机制(一) Python-基础-数据结构小结 '活动'设计的一些trick 一些简单的Python测试题 我的tmux配置及说明【k-tmux】 Review and Restart 工作四周年小结 vim插件: surround & repeat[成对符号编辑] vim插件: gundo[时光机] vim插件: expand-region[区域选中] vim插件: quickrun[快速执行] vim插件: trailing-whitespace[行尾空格处理] vim插件: closetag[成对标签补全] vim插件: ctrlp[文件搜索] vim插件: airline[状态栏增强] vim插件: theme[主题] vim插件: tagbar[大纲式导航] vim插件: nerdcommenter[快速注释] vim插件: rainbow_parentheses[括号高亮] vim插件: syntastic[语法检查] vim插件: delimitmate[符号自动补全] vim插件: matchit[成对标签跳转] vim插件: easy-align[快速对齐] vim插件: multiple-cursors[多光标操作] vim插件: vim-signature[快速标记跳转] vim插件: easymotion[快速跳转] vim插件: vundle[管理插件] Elasticsearch几个问题的解决 分享一份 Vim 简介PPT k-vim 更新9.0版本 关于知识管理工具的思考 Logstash+ElasticSearch+Kibana处理nginx访问日志
Go: logrus性能提升
2021-02-09 · via Posts on WKLKEN THINKING

在Go项目中, logrus是一个相对完备的第三方日志库

用起来非常顺手, 特别是WithField/WithFields/WithError

我们开发一些对性能要求非常高的应用, 例如API网关/权限服务等, 需要记录流水日志, 此时日志库的性能直接会影响整体接口性能.

所以针对性地做了一些优化

log.WithFields(fields).Info("-")

json formatter

logrus的默认json formatter json_formatter 使用的是标准库

众所周知, 性能比第三方库差很多

我们可以自定义一个json formatter, 使用例如 json-iterator/go来提升序列化性能

	// ! change here: use jsnoiter to do marshal. but ignore the entry.Buffer

	// var b *bytes.Buffer
	// if entry.Buffer != nil {
	// 	b = entry.Buffer
	// } else {
	// 	b = &bytes.Buffer{}
	// }

	// encoder := json.NewEncoder(b)
	// if f.PrettyPrint {
	// 	encoder.SetIndent("", "  ")
	// }
	// if err := encoder.Encode(data); err != nil {
	// 	return nil, fmt.Errorf("failed to marshal fields to JSON, %v", err)
	// }
	// return b.Bytes(), nil

	buf, err := jsoniter.Marshal(data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal fields to JSON, %v", err)
	}
	buf = append(buf, '\n')
	return buf, nil

null formatter 和 hooks

如果不指定, 默认的formatter是TextFormatter

func New() *Logger {
	return &Logger{
		Out:          os.Stderr,
		Formatter:    new(TextFormatter),
		Hooks:        make(LevelHooks),
		Level:        InfoLevel,
		ExitFunc:     os.Exit,
		ReportCaller: false,
	}
}

如果我们使用了 hooks (hooks.go)将日志转到文件/redis或其他渠道; 此时, 默认的Output和Formatter还是会被调用到;

其实我们配置了hooks, 那么原entry理论上是不需要的

func (entry Entry) log(level Level, msg string) {
    entry.fireHooks()
    ....
    entry.write()
}

func (entry *Entry) write() {
	entry.Logger.mu.Lock()
	defer entry.Logger.mu.Unlock()
	serialized, err := entry.Logger.Formatter.Format(entry)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
		return
	}
	if _, err = entry.Logger.Out.Write(serialized); err != nil {
		fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
	}
}

所以, 此时可以配置一个 NullFormatter, 将默认的链路关闭(do nothing), 日志只会走hooks

package formatter

import (
	"github.com/sirupsen/logrus"
)

// NullFormatter formats logs into text
type NullFormatter struct {
}

// Format renders a single log entry
func (f *NullFormatter) Format(entry *logrus.Entry) ([]byte, error) {
	return []byte(""), nil
}

重新设置log的配置

logger.SetOutput(ioutil.Discard)
logger.SetFormatter(&formatter.NullFormatter{})

logger.AddHook(NewRedisHook())

log entry pool

package logging

import (
	"sync"

	log "github.com/sirupsen/logrus"
)

type logEntryPool struct {
	pool sync.Pool
}

func newLogEntryPool() *logEntryPool {
	return &logEntryPool{
		pool: sync.Pool{
			New: func() interface{} {
				return &log.Entry{
					// Logger: logger,
					// Default is three fields, plus one optional.  Give a little extra room.
					Data: make(log.Fields, 6),
				}
			},
		},
	}
}

func (p *logEntryPool) Get(logger *log.Logger) *log.Entry {
	entry := p.pool.Get().(*log.Entry)
	entry.Logger = logger
	return entry
}

func (p *logEntryPool) Put(e *log.Entry) {
	// TODO: clean, should make?
	// reference: https://github.com/sirupsen/logrus/pull/796/files
	// e.Data = make(log.Fields, 6)
	e.Data = map[string]interface{}{}

	p.pool.Put(e)
}

使用

entry := logging.LogEntryPool.Get(logger)
defer logging.LogEntryPool.Put(entry)

异步日志

使用一个专门处理日志的goroutine

func (f *FileLogHook) makeAsync() {
	f.fireChannel = make(chan *logrus.Entry, f.asyncBufferSize)
	fmt.Printf("file hook will use a async buffer with size %d\n", f.asyncBufferSize)
	go func() {
		for entry := range f.fireChannel {
			if err := f.send(entry); err != nil {
				fmt.Println("Error during sending message to file:", err)
			}
		}
	}()
}

// Fire is called when a log event is fired.
func (f *FileLogHook) Fire(entry *logrus.Entry) error {
	if f.fireChannel != nil { // Async mode.
		select {
		case f.fireChannel <- entry: // try and put into chan, if fail will to default
		default:
			if f.asyncBlock {
				fmt.Println("the log buffered chan is full! will block")
				f.fireChannel <- entry // Blocks the goroutine because buffer is full.
				return nil
			}
			fmt.Println("the log buffered chan is full! will drop")
			// Drop message by default.
		}
		return nil
	}

  // Sync mode.
	return f.send(entry)
}

func (f *FileLogHook) send(entry *logrus.Entry) error {
	return f.loghook.Fire(entry)
}
Async write to redis 
with buffersize=10000
(block)

7700 -> 18000

Async write to redis
With buffersize=10000
(none block, drop if full)

60000+