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

推荐订阅源

S
Secure Thoughts
S
SegmentFault 最新的问题
云风的 BLOG
云风的 BLOG
T
Tailwind CSS Blog
博客园 - 聂微东
小众软件
小众软件
J
Java Code Geeks
MyScale Blog
MyScale Blog
人人都是产品经理
人人都是产品经理
量子位
GbyAI
GbyAI
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
M
MIT News - Artificial intelligence
Apple Machine Learning Research
Apple Machine Learning Research
有赞技术团队
有赞技术团队
月光博客
月光博客
B
Blog RSS Feed
D
DataBreaches.Net
酷 壳 – CoolShell
酷 壳 – CoolShell
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Cyberwarzone
Cyberwarzone
I
Intezer
The Register - Security
The Register - Security
博客园 - 【当耐特】
博客园 - 司徒正美
L
Lohrmann on Cybersecurity
U
Unit 42
N
News and Events Feed by Topic
S
Security Affairs
V
Visual Studio Blog
Y
Y Combinator Blog
Security Latest
Security Latest
Know Your Adversary
Know Your Adversary
Google DeepMind News
Google DeepMind News
大猫的无限游戏
大猫的无限游戏
S
Schneier on Security
P
Privacy International News Feed
TaoSecurity Blog
TaoSecurity Blog
Spread Privacy
Spread Privacy
G
Google Developers Blog
NISL@THU
NISL@THU
Project Zero
Project Zero
P
Palo Alto Networks Blog
Help Net Security
Help Net Security
宝玉的分享
宝玉的分享
Stack Overflow Blog
Stack Overflow Blog
S
Security @ Cisco Blogs
G
GRAHAM CLULEY
www.infosecurity-magazine.com
www.infosecurity-magazine.com

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: 异常时, 该提示用户哪些信息? Go: some libs Go: go-redis/cache升级的坑 Go: logrus性能提升 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访问日志
Better Code: 更好的异常日志打印
2022-01-16 · via Posts on WKLKEN THINKING

维护一个 N 年前的Python项目, 协助排查问题时发现, 原先的日志打印真真一言难尽

导致了一个问题, 同负责的运维 A 沟通, A 与其对接的甲方沟通……日志的缺失导致本来简单的问题变得复杂

如果日志打得更完备一点, 排查问题的难度不是一个的级别的

bad case

try:
    # got a data here
    # a lot of codes here
    # x = data["key"]
    # call_another_func(id, type)
    # call_another_func2(id, type, name)
except Exception:
    logger.error("error while do something")

这时候, 异常触发的时候, 日志只有

error while do something

相对于没打日志,只好了一点点, 知道有异常出现了, 问题是, 具体错误是什么?

第一次改进

try:
    # got a data here
    # a lot of codes here
    # x = data["key"]
    # call_another_func(id, type)
    # call_another_func2(id, type, name)
except Exception as error:
    logger.error("error while do something, error=%s", error)

此时报错时, 会打印

error while do something, error=App matching query does not exist

但是, 这里相对原始版本只好了一点点, 多了error message, 但是这行message并没有包含多少有利于问题排查的信息, 甚至连类型都没有, 看到了一脸懵

再次改进1

try:
    # got a data here
    # a lot of codes here
    # x = data["key"]
    # call_another_func(id, type)
    # call_another_func2(id, type, name)
except Exception as error:
    logger.exception("error while do something")

logger.error改成logger.exception, 此时错误日志

error while do something
Traceback (most recent call last) :
  File "a.py", line 674 in call_another_func
    app = get_app(id)
  File "b.py", line 18 in get_app
    app = App.objects.get(id)
DoesNotExist: App matching query does not exist.

这时候, 有详细的错误堆栈, 可以用于追溯错误原因.

但是, 某些场景下, 看到错误堆栈并不一定能确定问题, 根据堆栈信息回去看代码, 这代码不可能有问题……

此时, 可能是数据问题(各种原因的脏数据), 或者数据被人工改动(例如直接改表数据).

如果只有以上信息, 排查问题依旧要花很多时间.

再次改进2

增加数据/业务信息等上下文, 注意不好打敏感信息

try:
    # got a data here
    # a lot of codes here
    # x = data["key"]
    # call_another_func(id, type)
    # call_another_func2(id, type, name)
except Exception as error:
    logger.exception("error while do something [id=%s, type=%s, name=%s]", id, type, name)

错误信息

error while do something[id=1, type=test, name=abc]
Traceback (most recent call last) :
  File "a.py", line 674 in call_another_func
    app = get_app(id)
  File "b.py", line 18 in get_app
    app = App.objects.get(id)
DoesNotExist: App matching query does not exist.

更好的做法

使用自定义异常, 带code/message

class BaseException(Exception):
    def __init__(self, code, message):
        self.code = code
        self.message = message
        super().__init__(f"{self.code} {self.message}")
    def __str__(self):
        return f"<ErrorCode {self.code}:({self.message})>"

class XXException(BaseException):
    .....
  • 使用raise Error/CustomException替代try-except
  • 在统一的地方处理错误, 例如middleware或统一调用入口, try-except依次捕获所有已知的ErrorCustomException, 最后捕获通用的Exception(未知异常)

好处:

  1. 底层代码逻辑更简单, 少一层嵌套, 可读性更好
  2. 统一的错误声明及处理, 对于开发者及使用者都更为友好, 例如可以将错误码作为协议的一部分, 使用者根据错误码可以快速定位问题

Golang的error wrap

Go 的error wrap更为优雅, Working with Errors in Go 1.13

_, err := dosomething(type)
return fmt.Errorf("dosomething fail, type=`%s`, err=%w", type, err)

也可以看下之前 go 项目的实践: Go: 一种error wrap调用链处理方式

其他

  • 不要用print替代logging
  • 打很多日志等于没打
  • 谨慎确定当前要打日志的level
  • 尽量只在必要的地方打日志, 信息是有用的, 也是完备的, 有利于定位问题的
  • 一定不要掉异常
  • 如果使用try-except, Exception尽量精确, 打印日志尽量完备
  • 如果使用try-except, 捕获的代码块尽量小, 不会出现异常的语句不要放进去. 反例: 整个函数的所有代码都在一个try-except
  • logger 本身尽量按功能/模块/业务等切分多个, 反例: 全打标准输出