☰
Sliver 仓库中的 logtail 日志服务 API:Collection、Instance 与日志存取配置接口详解
2026/9/25 3:28:45 网站建设 项目流程
  • 网络安全

【免费下载链接】sliver

Adversary Emulation Framework

项目地址:https://gitcode.com/gh_mirrors/sl/sliver
点击查看免费下载

Sliver 仓库的vendor/tailscale.com/logtail目录内置了 Tailscale Logs Service 的完整客户端库与接口文档(api.md)。本文以该 API 文档为主体,逐节讲解 Collection/Instance 双级数据模型、日志存储、检索与配置四类 REST 接口的请求格式与语义,并结合仓库内logtail.go、config.go、buffer.go等源码实现,说明 Sliver 所依赖的tsnet组件是如何在实际运行中调用这套接口的。

一、日志服务总览与认证方式

Tailscale Logs Service 定义了一套 REST 接口,用于日志条目的配置、存储、检索与处理。HTTP 请求发送到服务的 base URLhttps://log.tailscale.com,响应为 JSON 编码并使用标准 HTTP 状态码。

配置类与检索类 API 的授权方式是将 secret API key 作为 HTTP Basic Auth 的 username传入。secret key 通过 base URL 下的 Web UI 生成。原文档给出的 curl 示例:

curl -u <log_api_key>: https://log.tailscale.com/collections

注意 password 部分留空,key 位于 username 位置。此外文档预告了未来的演进方向:后续会支持通过 HTTP 头指定 MessagePack 替代 JSON 作为响应/请求格式。

二、核心数据模型:Collection 与 Instance

Collection:以域名划分的日志分组

日志被组织进collection。每个 collection 内可以包含任意数量的instance。

  • Collection 的名字就是一个域名,是对相关日志的分组。文档建议:每个产品创建一个 collection,使用公司域名的子域名。
  • Collection 必须在存储任何日志之前先向 logs service 注册。

Instance:每台机器一个实例,private/public 双 ID

每个 collection 由若干 instance 构成,写日志的每台机器对应一个 instance。instance 有名字和编号,并持有两个 ID:

ID定义用途
private ID32 字节随机数,hex 编码用于写日志;唯一副本应留在发送日志的机器上,理想情况下在机器本地生成;生成后即可开始写日志
public IDprivate ID 的 SHA-256 哈希,hex 编码用于读日志和 adopt;设计上可以安全地发送给同时持有 logs service API key 的服务

Adoption 机制与保留策略

  • logs service 只会短期存储日志。要启用长期保留,需要用public ID + API key对日志执行adopt;adopt 之后日志按配置的保留期长期保留。
  • 未被 adopt的 instance 日志只临时保存,用于调试场景——比如一台配置错误、用坏 ID 写日志的机器,可以通过读取日志发现它。未 adopt 的日志存储有严格上限,12 小时后删除。

三、存储 API:POST /c/<collection-name>/<private-ID>

请求体为 JSON,有两种合法形态。

单条消息

单条消息是一个 JSON 对象。客户端可以在对象里放入任意自定义属性,唯一保留属性是logtail:

  • 在logtail对象内,客户端目前只能设置client_time属性,格式为 RFC3339(Go 参考格式"2006-01-02T15:04:05.999999999Z07:00")。
  • 文档预告的后续版本将支持client_time_offset(客户端重置以来的纳秒数,整数)与client_time_reset(布尔值,置 true 时重置时间偏移计数器)。服务端收到client_time_offset后,会以第一条(或 reset 那条)事件被接收时的server_time为基准换算出client_time。
  • 若在logtail对象中设置了其他属性,它们会被移入"error"字段,消息会被保存,并返回 4xx 状态码。

批量消息

批量消息是一个 JSON 数组,元素为单条消息对象:[ { }, { }, ... ]。

  • 若数组中某条目不是对象,该内容会被转成带"logtail": { "error": ... }属性的消息保存,并返回 4xx。
  • 其他不符合这两种格式的请求内容,同样被保存进 logtail error 字段并返回 4xx。
  • 非法 collection 名返回{"error": "invalid collection name"}加 403 状态码。

客户端行为建议

文档鼓励客户端:

  • 尽可能快地 POST(除非受电池续航限制)。这既缩短了日志在查看器中可见的时间,也降低了日志丢失概率;
  • 流式写日志时使用 HTTP/2,它更擅长维持 TLS 连接,减少后续 POST 的开销。

文档还预告未来版本将支持Content-Encoding: zstd的请求体压缩。

四、检索 API

GET /collections —— 查询 collections 与 instances

返回一个列出所有命名 collection 的 JSON 对象。调用方可通过 query 参数collection-name把结果限定到单个 collection。响应示例:

{ "collections": { "collection1.yourcompany.com": { "instances": { "<logid.PublicID>": { "first-seen": "timestamp", "size": 4096 }, "<logid.PublicID>": { "first-seen": "timestamp", "size": 512000, "orphan": true } } } } }

其中 instance 以 public ID 为键;orphan: true标记未被 adopt 的孤儿实例。

GET /c/<collection_name> —— 查询已存储的日志

可调用的 query 参数:

  • instances—— 零个或多个 instance,限定结果范围;
  • time-start—— 要包含的最早日志时间;
  • 以下三选一:
    • time-end—— 要包含的最新日志时间;
    • max-count—— 最大返回条数,用于分页;
    • stream—— 布尔值,保持响应挂起、像tail -f一样持续推送日志,与time-end不兼容。

两种响应模式:

  • stream=false:单个 JSON 对象,形如{ "logs": [ {}, {}, ... ] }(header 字段在原文档中标注为 TODO)。
  • stream=true:响应先给出一个与存储格式类似的 JSON header 对象,随后是逐行一个的 JSON 日志对象{...},服务端持续发送直到客户端关闭连接。

五、配置 API

对少量 instance 的组织,配置 API 最好由受信任的人工操作者(通常通过 GUI)调用;instance 数量多的组织则需要自动化 token 创建。

POST /collections —— 创建或删除 collection

调用方必须设置collection属性以及action=create或action=delete(form 编码或 JSON 编码均可)。字符集限制为[a-zA-Z0-9-_.]+。collection 名是全局命名空间,通常就是一个域名。

POST /instances —— 将 instance adopt 进 collection

必须发送以下属性(form 或 JSON 编码):

  • collection—— 合法 FQDN([a-zA-Z0-9-_.]+);
  • instances—— instance 的 public ID,hex 编码。

collection 名必须已被调用方所属的 group 认领。(collection-name, instance-public-ID)这一对可能已经有关联日志、也可能还没有。失败时返回 4xx/5xx 状态码和{"error": "what went wrong"}。

六、客户端侧实现:仓库内 logtail 库如何对接这套 API

以上接口的 Go 客户端就在仓库中,可作为接口语义的逐条印证。

URL 构造与写入路径

config.go 定义了DefaultHost = "log.tailscale.com"和Config结构体,字段与 API 概念一一对应:Collection(collection 域名)、PrivateID(写入用的私有 ID)、CopyPrivateID(一个覆盖本日志流的超集流的私有 ID)、BaseURL(留空时回落到默认 host)等。

logtail.go 的 NewLogger 中可以直接看到文档中POST /c/<collection-name>/<private-ID>路由的客户端形态:

url: cfg.BaseURL + "/c/" + cfg.Collection + "/" + cfg.PrivateID.String() + urlSuffix,

若设置了CopyPrivateID,还会附加?copyId=...查询参数,把同一批日志复制到超集流中。

批量数组格式与大小约束

drainPending 证实了文档中"批量消息是 JSON 数组"的说法:客户端把缓冲行拼成[开头、,分隔、]结尾的数组一次性上传。相关常量:

  • maxSize = 256 << 10:单条日志及单次上传体的上限 256KiB;
  • maxTextSize = 16 << 10:纯文本日志的上限,超出的部分会被截断并在字符串末尾追加省略标记(见 appendTruncatedString);
  • lowMemRatio = 4:低内存模式下上述上限除以 4,倾向"多次小批上传"而非攒大请求,避免 OOM。

Buffer抽象(buffer.go)是一个环形缓冲:TryReadLine读出日志行,无数据返回 nil、关闭返回 io.EOF;默认的MemoryBuffer以容量 256 的 channel 实现(低内存模式为 64)。

logtail 保留属性的客户端侧强制

文档规定logtail对象内只能出现client_time,客户端库同样执行该规则。appendMetadata 会写入client_time(RFC3339Nano)、proc_id(Config.IncludeProcID时为进程生成的一次性随机 ID)、proc_seq(IncludeProcSequence时的进程内自增序号),以及出错时的error.detail/error.bad_data子字段。

appendTextOrJSONLocked 则实现了"客户端消息中保留logtail属性"的语义:若用户传入的 JSON 对象自身含有顶层logtail键,库会将其剥离并改写为"logtail": {"error": {"detail": "duplicate logtail member", "bad_data": ...}}——这与文档中"多余 logtail 属性被移入 error 字段"的服务端规则互为镜像。

压缩、重试与网络感知

  • upload 对应文档预告的Content-Encoding: zstd:当Config.CompressLogs开启且响应体压缩后确有收益(节省超过 64 字节,扣除头部开销)时,会附带Content-Encoding: zstd与Orig-Content-Length头上传 zstd 帧。
  • 失败重试遵循标准语义:响应非 200 时解析Retry-After头作为等待时长,缺失时回退到 30~60 秒的随机退避;每次上传带 45 秒超时。
  • 库还会通过 netmon/eventbus 感知网络是否可达(awaitInternetUp),断网时暂停上传并等待"internet back up"事件,正好呼应文档中"尽快 POST 以降低丢日志概率"的建议——缓冲 + 唤醒机制让写操作不因网络阻塞。

刷新延迟与关闭语义

config.go 中defaultFlushDelay = 2 * time.Second,FlushDelayFn可自定义:返回 0 或负值立即上传,正值则按该延迟攒批。Logger.Shutdown会阻塞式地完成剩余上传(受传入 context 控制),Close是它的废弃别名;Disable()则可以在进程生命周期内全局停止上传。

七、Sliver 中的实际落点:tsnet 传输如何间接使用 logtail

Sliver 自身代码并未直接 importtailscale.com/logtail,而是经由tailscale.com/tsnet间接触达该服务,这决定了它的定位:logtail 在 Sliver 中服务于tsnet/Tailscale 传输通道,而不是 Sliver 自身的审计日志(后者走server/log/下的本地审计与日志轮转)。

  • server/transport/tailscale.go 实现了 tsnet gRPC 监听:要求环境变量TS_AUTHKEY,在tsnet应用目录下创建tsnet.Server并Listen出 gRPC 端点。
  • 被依赖的 tsnet.go 在启动时构建logpolicy.Config并调用logtail.NewLogger(L749-L777),集合名使用logtail.CollectionNode(即tailnode.log.tailscale.io,见 config.go),日志写入通过logf委托给s.logtail.Logf,并在退出时执行logtail.Shutdown。

因此,阅读 api.md 时应把握两点适用前提:其一,该接口是 Tailscale 官方日志服务的公有 API 规范,base URL 与认证方式以文档为准;其二,在本仓库的语境下,它是 tsnet 传输组件的遥测通道,开发者若需要为 Sliver 配置 Tailscale 传输,只需关注TS_AUTHKEY与 tsnet 目录,logtail 的写入行为由依赖库自动完成。

八、小结

  • 数据模型:Collection(域名,全局命名空间,需先注册)→ Instance(每台写日志的机器一个,private/public 双 ID);private ID 只留本机用于写入,public ID 配合 API key 用于读取与 adopt,未 adopt 的日志 12 小时后删除。
  • 存储:POST /c/<collection>/<private-ID>,单对象或对象数组,logtail为保留属性(当前仅client_time),非法内容被降级为logtail.error并返回 4xx。
  • 检索:GET /collections查拓扑,GET /c/<collection>按instances/time-start加time-end/max-count/stream分页或流式拉取。
  • 配置:POST /collections建/删 collection,POST /instances完成 adopt。
  • 仓库内的 logtail.go、config.go、buffer.go 是这套接口的完整参考客户端,Sliver 通过 tsnet 集成 在 tsnet 传输中实际使用它。
  • 网络安全

【免费下载链接】sliver

Adversary Emulation Framework

项目地址:https://gitcode.com/gh_mirrors/sl/sliver
点击查看免费下载
上一篇:Android Debug Database混淆映射文件终极指南:mapping.txt深度解析与实战应用
下一篇:antd Badge dot 模式详解:无数字小红点的显示规则、源码实现与样式定制

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询