日志是排查线上问题最重要的依据,但日志文件散落在各个目录里,格式五花八门,想找一条关键记录往往要grep半天。Fluentd是一个成熟的日志采集转发工具,它可以从各种数据源读取日志,经过过滤和格式化后写到不同的输出端。SQLite则是一个零依赖的嵌入式数据库,单个文件就能存下千万级日志记录,配合索引查询速度非常可观。把这两者组合起来,不需要额外部署 Elasticsearch 之类的重量级组件,就能搭出一套适合中小规模业务的日志收集系统。本文完整走一遍搭建流程,并重点讲清楚配置细节和性能优化。

一、整体架构设计
这套方案的核心思路是:Fluentd作为采集层,部署在每台需要收集日志的机器上,tail插件监控日志文件的变化,解析成结构化记录;SQLite作为存储层,通过SQL输出插件把记录写入本地或远端共享的数据库文件中。查询时直接用SQL语句,或者用任何支持SQLite的工具连接查询。
为什么选SQLite而不是MySQL或者Elasticsearch?关键在于运维成本。SQLite不需要独立进程,不需要配置账号密码,备份就是复制一个文件。对于日均日志量在百万条以内、只需要单机查询的场景,SQLite的性能完全够用,而且资源占用极低,一个内存只有1GB的小机器也能轻松跑起来。
需要注意的一点是,SQLite是文件级锁,写操作会锁整个数据库。如果有多台机器同时往同一个数据库文件写日志,必须把Fluentd的缓冲参数调好,避免写入冲突。更稳妥的做法是每台机器写自己的本地库,查询时再聚合,或者由一台中心Fluentd统一写入。
二、Fluentd的安装与采集配置
Fluentd官方推荐用td-agent安装包部署,它自带了Ruby运行环境和常用插件,省去了手工处理依赖的麻烦。以CentOS为例,安装命令如下:
# 安装td-agent curl -fsSL https://toolbelt.treasuredata.com/setup/install-redhat-td-agent4.sh | sh yum install -y td-agent # 启动并设置开机自启 systemctl start td-agent systemctl enable td-agent
装好之后开始写采集配置。Fluentd的配置文件在/etc/td-agent/td-agent.conf,核心是source段和match段。source定义日志从哪来,match定义日志到哪去。下面是一个监控Nginx访问日志的例子:
<source>
@type tail
@id nginx_access
path /var/log/nginx/access.log
pos_file /var/log/td-agent/nginx-access.pos
tag nginx.access
<parse>
@type nginx
expression /^(?<remote>[^ ]+) (?<host>[^ ]+) (?<user>[^ ]+) \[(?<time>[^\]]+)\] "(?<method>\S+)(?: +(?<path>[^ ]*) +\S*)?" (?<code>[^ ]+) (?<size>[^ ]+) "(?<referer>[^"]*)" "(?<agent>[^"]*)"/
time_format %d/%b/%Y:%H:%M:%S %z
</parse>
</source>几个参数值得说明一下。path指定被监控的日志文件;pos_file记录读取到的文件位置,Fluentd重启后能从断点继续读,这个文件一定不能省,否则重启会导致日志重复采集或漏采;tag是这条日志流的标签,后面的match段靠它做路由匹配。
如果日志格式不固定,也可以用正则解析,或者干脆先用@type none整行读入,再在filter段里慢慢拆。filter段可以做字段增删、类型转换,比如给每条日志打上主机名标记,方便后续区分来源:
<filter nginx.**>
@type record_transformer
<record>
hostname ${hostname}
collected_at ${time}
</record>
</filter>三、写入SQLite的配置细节
Fluentd写SQLite需要用到fluent-plugin-sql插件,先安装它:
td-agent-gem install fluent-plugin-sql
装完后,先在SQLite里建好日志表和索引。表结构设计要贴合日志特点,时间字段建索引是必须的,因为绝大多数查询都带时间范围条件:
CREATE TABLE IF NOT EXISTS nginx_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, remote TEXT, host TEXT, method TEXT, path TEXT, code INTEGER, size INTEGER, referer TEXT, agent TEXT, hostname TEXT, logged_at DATETIME ); CREATE INDEX IF NOT EXISTS idx_logs_time ON nginx_logs(logged_at); CREATE INDEX IF NOT EXISTS idx_logs_code ON nginx_logs(code);
然后配置match段,把带nginx.前缀的日志全部写入这个表:
<match nginx.**>
@type sql
host 127.0.0.1
adapter sqlite3
database /data/logs/logs.db
username root
password ""
remove_prefix nginx
<table>
table nginx_logs
column_mapping 'remote,host,method,path,code,size,referer,agent,hostname,time:logged_at'
time_format '%Y-%m-%d %H:%M:%S'
</table>
<buffer>
@type file
path /var/log/td-agent/buffer/nginx
flush_interval 5s
chunk_limit_size 8MB
flush_thread_count 2
retry_max_interval 30s
</buffer>
</match>这里有几个容易踩坑的地方。第一,SQLite的adapter是sqlite3,database参数直接写数据库文件路径,用户名密码随便填,实际不生效。第二,column_mapping负责把Fluentd记录里的字段映射到表列,格式是逗号分隔的记录字段名:表列名,不写冒号表示两者同名。第三,buffer段非常关键,它决定了写入的批量和频率。SQLite的事务提交有开销,如果每条日志都单独提交一次,写入吞吐会非常差。把flush_interval设置成5秒左右,让插件攒一批记录在一个事务里批量写入,性能能提升一个数量级。
四、日志清理与运维优化
日志库文件会持续增长,必须定期清理旧数据。SQLite的DELETE删除数据后,文件体积不会自动缩小,需要配合VACUUM回收空间。比较优雅的做法是写一个定时任务,每天凌晨删除N天前的日志,并在低峰期执行一次增量清理:
DELETE FROM nginx_logs WHERE logged_at < datetime('now', '-30 days');
PRAGMA incremental_vacuum;如果想避免VACUUM带来的锁表影响,可以在建库时开启自动增量清理模式:先执行PRAGMA auto_vacuum = incremental;(必须在建表之前设置),之后每次incremental_vacuum只会回收少量页面,不会长时间阻塞写入。
性能方面还有几个实用经验。开启WAL模式能显著提升并发读写能力,执行PRAGMA journal_mode = WAL;后,读查询不再被写操作阻塞,Fluentd写入和人工查询可以同时进行。适当调大PRAGMA cache_size(比如设为负值-8000表示8MB缓存)能让大批量写入更顺滑。另外建议把数据库文件放在独立磁盘上,避免日志写入高峰影响业务IO。
日常运维时,可以定期统计库的规模,用SELECT COUNT(*) FROM nginx_logs;结合文件大小估算存储趋势,据此调整保留周期。查询慢的语句用EXPLAIN QUERY PLAN检查是否走索引,如果发现全表扫描,通常是查询条件里的时间格式和存储格式不一致导致的,比如存的是UTC时间而查询用的是本地时间,对齐后速度立刻恢复正常。
整套方案跑起来后,一条从采集、缓冲、落库到查询的链路就打通了。对于单机或几台机器规模的日志管理需求,这套组合投入小、见效快,后期如果日志量上来了,只需把match段的输出换成别的插件,采集侧的配置几乎不用动,扩展路径也很清晰。
SQLite日志存储Fluentd日志收集日志系统搭建修改时间:2026-09-10 05:50:36