1. 这不是博客,是程序员的思维操作系统
“Programming.log”——光看这个名字,你可能以为是个技术博客、个人网站,或者某个开源项目文档站。但其实它更接近一个 程序员的思维操作系统 :没有首页、没有分类导航、不追求流量、不设评论区,甚至不强调“发布”动作。它是一份持续演进的、私密的、结构化的编程思考日志。我从2018年开始用纯文本+Git管理自己的 programming.log,至今累计327个独立条目,平均每周新增1.8条,最长单条记录达4200字。它解决的从来不是“怎么写文章”,而是“如何让思考不流失”这个根本问题。核心关键词—— 编程日志、思维留痕、增量式写作、代码即注释、可检索知识库 ——全部指向一个事实:绝大多数程序员的深度思考,都死在了编辑器关闭的那一刻。而 programming.log 的设计哲学,就是把“关编辑器”这个动作,变成“存档思考”的自然终点。它适合三类人:刚脱离教程阶段、开始写真实业务代码的中级开发者;习惯用 Stack Overflow 解决问题但总记不住底层原理的实践者;以及那些写了十年代码,却说不清自己技术判断依据的资深工程师。这不是知识管理工具的又一个变体,它是对“编程即思考”这一本质的回归——代码是思考的产物,日志是思考的副产品,而二者必须共生。
2. 内容整体设计与思路拆解
2.1 为什么拒绝传统博客架构?——从“展示”到“沉淀”的范式转移
我试过搭建 Hexo 博客、用 Notion 做知识库、甚至尝试过 Obsidian 的双向链接网络。结果无一例外:前三个月热情高涨,半年后归于沉寂。根本原因在于,它们默认的交互模型是“面向他人展示”。Hexo 要选主题、配 CDN、调 SEO;Notion 要设计数据库视图、维护标签体系;Obsidian 要刻意建立链接、担心“孤岛笔记”。这些动作都在消耗本该用于思考的能量。而 programming.log 的起点,是一个极其朴素的 Unix 哲学:
每个文件只做一件事,并且把它做好
。它的根目录下只有两类东西:
entries/
(按日期命名的 Markdown 文件)和
refs/
(被频繁引用的通用概念速查表)。没有
categories/
,没有
tags/
,没有
archives/
。因为分类本身就是一个强认知负担——当你在写“为什么 React 的 useEffect 依赖数组容易出错”时,你不会先想“这该归到‘前端’还是‘React’还是‘调试技巧’”,你会直接敲下
2024-05-12-react-useeffect-dependency-trap.md
。这个文件名本身就是元数据,它包含时间戳(便于按时间线回溯思维演进)、领域标识(react)、问题特征(dependency-trap),比任何人工打标都精准。实测下来,这种命名法让后续检索效率提升3倍以上:
git grep -l "useEffect.*dependency"
能瞬间定位所有相关条目,而无需维护标签同步逻辑。
2.2 “Log”而非“Blog”的底层逻辑:时间序列即知识图谱
传统博客按“主题聚合”,programming.log 按“时间流沉淀”。这看似退步,实则是对程序员工作流的深度适配。我们每天面对的不是静态知识,而是动态演进的问题域:昨天在调试 Node.js 的 event loop 阻塞,今天在优化 PostgreSQL 的索引策略,明天要评估 Rust 的所有权模型是否适合新服务。这些看似割裂的点,在时间轴上天然形成关联。比如我在
2023-11-03-nodejs-event-loop-blocking.md
中记录了用
async_hooks
定位长任务的全过程,三个月后写
2024-02-17-postgres-index-strategy.md
时,自然会引用前一条:“类似 event loop 阻塞的诊断思路,这里也适用‘分层隔离’原则——先确认是查询慢还是锁等待……”。这种跨时间点的引用,不是靠标签系统强行关联,而是思考路径的真实延伸。Git 的 commit history 就是天然的知识图谱:
git log --oneline --grep="postgres"
能看到所有涉及数据库的思考迭代,
git blame 2024-02-17-postgres-index-strategy.md
能追溯某段索引建议的原始讨论上下文。我甚至用 Git hooks 自动提取每次 commit 的关键词生成简易索引页,整个过程零配置、零维护。这比任何第三方知识图谱工具都更贴合程序员的肌肉记忆——毕竟,我们每天都在和 Git 打交道,何必另学一套?
2.3 极简结构背后的工程权衡:为什么不用数据库或全文搜索引擎?
有人问:既然要检索,为什么不直接上 Elasticsearch?答案很实在:
99% 的编程思考日志,其检索需求远低于你的想象
。我的全部 327 条日志,总大小仅 4.2MB,用
ripgrep
(rg)在 SSD 上全文搜索平均耗时 12ms。而部署 Elasticsearch 至少需要 2GB 内存、专用运维监控、定期备份策略——为 12ms 的搜索提速,付出的复杂度成本高得离谱。更关键的是,数据库抽象层会切断“思考”与“代码”的物理连接。在 programming.log 中,一段关于内存泄漏的分析,可以直接嵌入真实的 heap dump 分析命令:
# 从生产环境抓取的 heap dump 分析片段
$ jmap -histo:live 12345 | head -20
num #instances #bytes class name
----------------------------------------------
1: 42844 21562240 [B
2: 38211 12227520 java.util.HashMap$Node
3: 38211 9170640 com.example.cache.UserCacheEntry
这段命令不是截图,不是伪代码,而是真实可执行的。当半年后你再看到这条日志,只需复制粘贴就能复现当时的诊断现场。如果放进数据库,这些命令要么被转义丢失,要么需要额外的渲染层还原。programming.log 的极简主义,本质是 用存储格式的透明性,换取执行路径的确定性 。它不追求“智能推荐”,只确保“下次遇到同样问题,我能原样复现”。
3. 核心细节解析与实操要点
3.1 文件命名规范:让机器和人都能读懂的语义化标识
文件名是 programming.log 的第一道知识入口,必须同时满足人类可读和机器可解析。我采用四段式命名法:
YYYY-MM-DD-domain-problem-scope.md
。以最近一条为例:
2024-05-22-rust-ownership-borrow-checker-lifetime-annotation.md
。拆解如下:
-
2024-05-22:ISO 8601 格式日期,保证字典序即时间序,ls entries/ | head -5就是最近五条; -
rust:技术领域,限定在 15 个常用词内(如go,k8s,postgres,linux-kernel),避免rust-lang或rust-programming等冗余后缀; -
ownership-borrow-checker:核心概念组合,用连字符连接,不缩写(ocbc会失去语义); -
lifetime-annotation:具体问题场景,描述现象而非结论(不写fix-lifetime-error,因为错误可能有多种解法)。
提示:命名时禁止使用空格、中文、特殊符号(如
&,#,@),这是为了兼容所有 shell 环境。曾有一次我误用2024-03-15-python-asyncio-“got-coroutine”-error.md(带中文引号),导致git add失败且报错晦涩,排查半小时才发现是文件名问题。现在所有新建文件都通过脚本生成:./new-entry rust ownership-borrow-checker lifetime-annotation,自动创建文件并填充标准头部。
3.2 条目模板:结构化但不僵化的内容骨架
每条日志开头必须包含标准化 YAML front matter,这是后续自动化处理的基础:
---
title: "Rust 中生命周期标注的直觉化理解"
date: 2024-05-22
tags: [rust, ownership, borrow-checker]
related: ["2023-11-03-rust-borrow-checker-errors.md", "2024-02-10-rust-lifetime-elision.md"]
status: draft # draft / reviewed / archived
---
-
title是人类阅读的友好标题,可含空格和标点; -
date与文件名日期一致,用于生成时间线视图; -
tags是轻量级分类,仅用于快速过滤(rg -g "*.md" "tags:.*rust" entries/); -
related字段手动维护,指向其他条目,形成最小可行的知识链接; -
status控制可见性,archived条目会被静态生成器忽略。
正文部分采用“问题驱动”结构:
【现象】
→
【复现步骤】
→
【根本原因】
→
【验证方法】
→
【解决方案】
→
【延伸思考】
。
例如在分析
std::sync::Mutex
性能问题时,“现象”写“API 响应 P99 从 120ms 突增至 850ms”,而不是“Mutex 性能差”;“复现步骤”精确到
cargo test -- --test-threads=1
,确保可重现;“根本原因”必须引用 Rust RFC 文档编号(如 RFC 1236),而非泛泛而谈。这种结构强迫你区分“观察”和“推论”,避免日志变成主观臆断的集合。
3.3 代码与日志的共生设计:让日志成为可执行的文档
programming.log 的核心竞争力,在于它模糊了“文档”和“代码”的边界。所有代码块必须满足三个条件:
-
可复制粘贴执行
:禁用
...省略号,完整展示命令和预期输出; - 带上下文注释 :在代码块上方说明“此命令用于验证 X 假设”;
-
版本锁定
:明确标注工具版本,如
# 使用 rustc 1.77.2 (aedd7b48d 2024-03-17)。
实际案例:一条关于
kubectl top nodes
数据不准的日志,不仅记录了
kubectl top nodes --heapster-url=...
的修复命令,还附带了完整的诊断链:
# 步骤1:确认 metrics-server 是否正常
$ kubectl get apiservice v1beta1.metrics.k8s.io
NAME SERVICE AVAILABLE AGE
v1beta1.metrics.k8s.io kube-system/metrics-server True 14d
# 步骤2:检查 metrics-server 日志中的证书错误(关键!)
$ kubectl logs -n kube-system deploy/metrics-server | grep -i "x509"
E0522 10:23:41.221789 1 scraper.go:139] "Failed to scrape node" err="x509: certificate signed by unknown authority"
# 步骤3:临时绕过证书验证(仅用于诊断,非生产方案)
$ kubectl top nodes --insecure-skip-tls-verify
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
node-1 120m 6% 4.2Gi 32%
这段日志的价值,在于它把一次故障排查变成了可复用的模式:当未来遇到任何
x509
相关的 Kubernetes 组件通信失败,这个诊断链就是第一响应手册。它不需要你记住“metrics-server 证书问题”,只需要记住“
kubectl logs -n kube-system deploy/metrics-server | grep x509
是万能起点”。
4. 实操过程与核心环节实现
4.1 初始化:三分钟搭建你的第一个 programming.log
无需安装任何新工具,只要你的机器装有 Git 和任意文本编辑器。以下是零配置初始化流程:
- 创建仓库并设置忽略规则 :
mkdir programming.log && cd programming.log
git init
echo "entries/*.md" > .gitignore
echo "refs/*.md" >> .gitignore
echo ".DS_Store" >> .gitignore
git add .gitignore && git commit -m "chore: init repo with ignore rules"
-
生成首条日志模板
:
创建new-entry脚本(保存为./new-entry,赋予执行权限chmod +x ./new-entry):
#!/bin/bash
# Usage: ./new-entry <domain> <concept> <detail>
DATE=$(date +%Y-%m-%d)
DOMAIN=$1
CONCEPT=$2
DETAIL=$3
FILENAME="entries/${DATE}-${DOMAIN}-${CONCEPT}-${DETAIL}.md"
cat > "$FILENAME" << EOF
---
title: ""
date: $DATE
tags: [$DOMAIN, $CONCEPT]
related: []
status: draft
---
## 【现象】
## 【复现步骤】
## 【根本原因】
## 【验证方法】
## 【解决方案】
## 【延伸思考】
EOF
echo "Created: $FILENAME"
code "$FILENAME" # 自动用 VS Code 打开,可替换为 vim/nano
运行
./new-entry linux kernel oom-killer process-selection
,立即生成结构化文件并打开编辑。整个过程不到 90 秒,且后续所有条目都保持统一结构。
4.2 日常维护:如何让日志真正融入开发工作流
最大的陷阱是把 programming.log 当成“额外任务”。我的解决方案是: 让它成为开发闭环的最后一个环节 。每当完成以下任一动作,立即创建或更新日志:
- 解决一个 Stack Overflow 上没答案的问题 :此时思考最清晰,直接记录“为什么官方文档没提这个坑”;
-
Code Review 中发现重复模式
:如三次看到同事用
for i in range(len(arr)),就写2024-05-20-python-iterating-with-range-len-anti-pattern.md; -
升级依赖后出现兼容性问题
:记录
cargo update后的具体 break change,比 Changelog 更聚焦你的场景。
我用 Git hook 自动提醒:在
.git/hooks/pre-commit
中添加:
#!/bin/bash
# 检查是否修改了 entries/ 下的文件,若未修改则警告
if ! git status --porcelain | grep -q "^M.*entries/"; then
echo "⚠️ Warning: No programming.log entry updated. Consider documenting this change."
echo " Run: ./new-entry <domain> <topic> <detail>"
fi
这个 hook 不阻止提交,只温柔提醒。三个月后,92% 的提交都关联了日志条目,因为它已内化为“写完代码就写日志”的肌肉反射。
4.3 检索与复用:超越 grep 的高效知识召回
当条目超过 200 条,单纯
rg
会遇到瓶颈。我的进阶方案是三层检索体系:
第一层:语义化 grep
用
--max-count=3
限制返回条数,避免信息过载:
# 查找所有涉及“context deadline exceeded”的 Go 错误分析
rg --max-count=3 "context deadline exceeded" entries/
第二层:Git-aware 搜索
利用 Git 的语义理解能力:
# 查看某条日志的历史变更,理解思考如何演进
git log -p entries/2024-03-15-go-context-deadline-exceeded.md
# 找出谁在最近一周修改过 Rust 相关日志(团队协作时有用)
git log --since="1 week ago" --oneline --grep="rust" entries/
第三层:轻量级索引生成
用 Python 脚本(
gen-index.py
)每日生成
INDEX.md
:
import glob
import re
from datetime import datetime
entries = sorted(glob.glob("entries/*.md"), reverse=True)
with open("INDEX.md", "w") as f:
f.write("# Programming.log Index\n\n")
for entry in entries[:50]: # 只索引最新50条
with open(entry) as e:
lines = e.readlines()
title = re.search(r'title: "(.*)"', lines[1]).group(1)
date = re.search(r'date: (\d{4}-\d{2}-\d{2})', lines[2]).group(1)
f.write(f"- [{date}] [{title}]({entry})\n")
配合
crontab -e
添加
0 9 * * * cd /path/to/programming.log && python gen-index.py
,每天早上自动生成最新索引。这个索引不追求全量,只服务“最近高频问题”,符合大脑的记忆规律——你最可能复用的,永远是最近两周的思考。
5. 常见问题与排查技巧实录
5.1 “写了几十条,但根本找不到要用的那条”——检索失效的根源与解法
这是新手最高频的挫败感。根本原因往往不是工具问题,而是 命名与内容的语义断裂 。典型案例如下:
-
❌ 错误命名:
2024-01-10-bug-fix.md—— “bug” 过于宽泛,无法关联到具体技术点; -
❌ 内容失焦:在
2024-02-15-k8s-pod-stuck-terminating.md中大篇幅描述业务逻辑,却没写清kubectl delete pod --grace-period=0这个关键命令。
我的排查清单:
| 症状 | 根因 | 解法 |
|---|---|---|
rg "mysql deadlock" entries/
返回 0 结果
| 实际日志中写的是 “MySQL 锁表” 或 “InnoDB 死锁” |
统一术语:在
refs/glossary.md
中定义 “deadlock” 必须指代数据库事务死锁,所有条目强制使用该术语
|
想找“Docker 构建缓存失效原因”,但
rg "docker cache" entries/
结果太多
|
缓存失效是现象,根本原因是
COPY . .
放置位置不当
|
在
refs/docker-cache-rules.md
中固化最佳实践,所有相关日志必须引用此文件 ID
|
实操心得:每月花 15 分钟执行
rg -o "\b([a-z]+)-([a-z]+)\b" entries/ \| sort \| uniq -c \| sort -nr \| head -10,找出高频但未标准化的术语组合,立即补充到refs/glossary.md。这个操作让我在三个月内将检索成功率从 63% 提升至 91%。
5.2 “日志越写越多,但感觉没沉淀出体系”——从碎片到系统的跃迁路径
单条日志是原子,系统是分子。构建系统的关键在于 主动建立跨条目的约束关系 。我的方法是“三叉戟模型”:
-
时间锚点
:每季度末,用
git log --since="3 months ago" --oneline --grep="k8s\|docker" entries/提取本季度所有容器相关条目,手动梳理成Q2-2024-k8s-lessons-learned.md,提炼出三条通用原则(如“所有 DaemonSet 必须设置 tolerations”); -
概念聚类
:用
rg -l "tls.*handshake" entries/找出所有 TLS 握手问题,合并分析共性,生成refs/tls-handshake-patterns.md,归纳出“证书链缺失”、“SNI 不匹配”、“协议版本降级”三大模式; -
反模式库
:专门维护
refs/anti-patterns.md,收录如 “在 CI 中硬编码 secret”、“用SELECT *查询大表” 等,每条注明触发场景、检测方法、修复成本。当新日志涉及反模式时,必须引用此文件 ID。
这个过程不追求一次性建成,而是像编译器一样增量链接。某天你突然发现
2024-04-05-golang-http-timeout.md
和
2024-05-12-rust-reqwest-timeout.md
都在讨论“客户端超时的传播链”,这时自然会产生
refs/client-timeout-propagation.md
。系统不是设计出来的,是在日志生长过程中自然结晶的。
5.3 “团队想共用,但怕泄露敏感信息”——安全与协作的平衡术
programming.log 的私密性是其力量来源,但团队协作时需谨慎。我的实践是“三层隔离”:
-
物理隔离
:个人日志用私有 Git 仓库,团队共享部分用独立仓库
team-programming-log,通过 Git subtree 同步公共条目; -
内容脱敏
:所有日志模板强制包含
# SENSITIVE: true/false字段,pre-commithook 拦截SENSITIVE: true的条目提交到共享仓库; -
自动化红队
:用
sed脚本扫描待提交文件,自动替换敏感模式:# 替换 AWS 密钥(正则匹配典型格式) sed -i 's/ASIA[0-9A-Z]\{16\}/[REDACTED_AWS_ACCESS_KEY]/g' "$FILE" # 替换数据库连接串 sed -i 's/postgres:\/\/.*@/postgres:\/\/[REDACTED]@/g' "$FILE"
注意:绝不依赖人工审查敏感信息。曾有一次同事在日志中写了
curl -H "Authorization: Bearer xxx",以为 token 会过期就没事,结果被爬虫抓取。现在所有 HTTP 请求头、配置文件片段,都必须经过redact.sh脚本处理才允许提交。安全不是功能,是日志的默认属性。
6. 工具链与生态扩展
6.1 从纯文本到可交互文档:VS Code 插件定制
纯文本的优势是普适性,但牺牲了部分交互体验。我的折中方案是: 用 VS Code 插件增强,但不改变文件格式 。核心插件组合:
-
Markdown All in One
:提供快捷键
Ctrl+K Ctrl+T快速跳转到refs/下的术语定义; -
Todo Tree
:将日志中的
TODO:标记为待办事项,集中管理技术债; -
Code Spell Checker
:校验技术术语拼写(如
kubernetes不是kubernettes),避免检索失效。
最关键的自定义是
settings.json
中的代码片段:
"snippets": {
"markdown": [
{
"prefix": "log-header",
"body": [
"---",
"title: \"${1:Title}\"",
"date: ${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE}",
"tags: [${2:domain}, ${3:concept}]",
"related: []",
"status: draft",
"---",
"",
"## 【现象】",
"",
"## 【复现步骤】",
"",
"## 【根本原因】",
"",
"## 【验证方法】",
"",
"## 【解决方案】",
"",
"## 【延伸思考】"
]
}
]
}
输入
log-header
+ Tab,瞬间生成标准头部。这个片段不增加学习成本,却将模板填充时间从 30 秒压缩到 2 秒,让“写日志”的阻力趋近于零。
6.2 与现有开发工具链的无缝集成
programming.log 的价值,在于它不创造新工具,而是激活已有工具。三个深度集成案例:
① 与 GitHub Issues 关联
在 Issue 描述中写
Related to programming.log: 2024-05-22-rust-ownership-borrow-checker.md
,GitHub 会自动渲染为链接。当 Issue 关闭时,我在日志中追加
# Resolved in PR #1234
,形成双向追溯。
② 与 CI/CD 流水线联动
在
.github/workflows/test.yml
中添加步骤:
- name: Check log consistency
run: |
# 确保所有新日志都包含必要字段
for file in $(git diff --name-only HEAD~1 | grep "entries/.*\.md"); do
if ! grep -q "title:" "$file"; then
echo "ERROR: $file missing title field"
exit 1
fi
done
这迫使团队成员遵守最低质量标准,避免产生“半成品日志”。
③ 与终端命令历史打通
在
~/.zshrc
中添加:
# 将当前命令追加到最新日志(需先打开日志文件)
alias logcmd='echo "# Command: $(history 1 | sed "s/^[ ]*[0-9]*[ ]*//")" >> "$(ls -t entries/*.md | head -1)"'
调试时执行
curl -v https://api.example.com
,随后输入
logcmd
,日志中立即追加
# Command: curl -v https://api.example.com
。这个小技巧让“记录操作”变得比“复制命令”还快。
7. 个人经验与长期实践体会
我在实际使用中发现,programming.log 最大的价值不在“写”的时候,而在“不写”的时候。去年重构一个遗留 Go 服务,连续三天卡在 goroutine 泄漏问题上。第四天早上,我放弃调试,打开
2023-08-15-go-goroutine-leak-detection.md
,里面详细记录了上次用
pprof
抓取 goroutine dump 的完整命令链,以及一个关键提示:“注意 net/http.serverHandler.serve 的 goroutine 数量是否随请求线性增长”。我照着执行,15 分钟内定位到问题——一个未关闭的
http.Client
被反复创建。这个过程没有新知识输入,全是旧日志的精准召回。它证明了一件事:
编程日志不是知识的终点,而是思考的缓存
。CPU 有 L1/L2 缓存,程序员的大脑也需要一个 L3 缓存,而 programming.log 就是那个用磁盘实现的、永不遗忘的 L3。
踩过几次坑之后,我彻底放弃了“等有空再系统整理”的幻想。现在我的信条是:
思考的保质期只有 24 小时
。一个灵光乍现的优化思路,如果不在当天写成日志,一周后你只会记得“好像有个好办法”,但再也想不起具体是什么。所以我的桌面永远开着一个
entries/
目录的终端窗口,
ls -t | head -5
就是我的今日思考快照。这个习惯带来的改变是静默而深远的:我的技术决策越来越有依据,Code Review 时能快速指出“这个设计在
2024-03-10-microservice-circuit-breaker.md
中验证过风险”,新人入职时,我的日志比公司 Wiki 更能回答“为什么我们这样设计”。
最后再分享一个小技巧:每年 12 月 31 日,我会执行
git log --oneline --since="1 year ago" | wc -l
,统计年度思考产出。数字本身不重要,重要的是这个动作带来的仪式感——它提醒我,这一年里,有多少次深夜调试、多少次灵光闪现、多少次踩坑后的真实领悟,都被稳稳地存档在那个叫
programming.log
的文件夹里。它不宏大,不炫技,只是安静地躺在那里,等待下一次被需要。

992

被折叠的 条评论
为什么被折叠?



