mcpField
P
portal-logging

portal-logging

github.com/xgent-aiクラウド・DevOps1110 回インストール今日更新
リポジトリ

portal-logging について

portal-loggingは、アプリのログをポータルの「ログとモニタリング」アプリに接続するスキルです。アプリのデプロイや障害対応を担うチーム向けに、認証情報、収集・送信方法、検証とトラブルシューティングを説明し、監査や課金データとの違いも示します。

主な機能

  1. 01コレクターとアプリ内送信の方式
  2. 02サービス認証情報の設定
  3. 03バッチ送信と応答の検証
  4. 04ログ項目とレベルの規約
  5. 05クエリとトラブルシューティング

活用シーン

  1. 01コンテナやファイルログの収集設定
  2. 02アプリから構造化ログを一括送信
  3. 03送信を検証してポータルでログ検索
  4. 04認証、権限、ストリーム名の問題を調査

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/xgent-ai/skills --skill portal-logging

インストール後、Claude Code で/portal-loggingを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
collectors.md
contract.md
scripts/
smoke-ingest.sh

詳細

プロジェクトの README より

portal-logging · 把 App 的日志接进「日志与监控」

一条链路四段,两种身份:

你的进程  →  采集器  →  POST /svc/observability/v1/ingest/<stream>  →  在「日志与监控」App 里看
(产生)     (搬运)    ↑ 服务身份(机器的钥匙)                        ↑ 用户身份(人的票)

两种身份不能互换,这是整条链路最常见的困惑来源:采集器只能写、不能查;人只能查、不能写。拿服务密钥去查会得到 401,那不是配置错。

路径约定:本 skill 里的 apps/… deploy/… docs/… 都在门户仓。在你自己的 App repo 里它们不存在——它们标注的是「门户侧的事实在哪」,不是让你去打开的文件。你需要的一切在本 skill 的 references/ 与 scripts/(自包含,路径相对于本 SKILL.md 所在目录)。运行脚本前,在 App repo 根目录按实际加载位置设置 SKILL_DIR="<本 SKILL.md 所在目录>"。

按任务读

任务读
配采集器(容器 stdout / 文件 tail / 应用内直打),含多行合并与级别判定references/collectors.md
端点、凭据、请求/响应形状、查询 API、错误码矩阵、计量references/contract.md
只想先打通一条,验证链路是活的"$SKILL_DIR/scripts/smoke-ingest.sh"(改环境变量直接跑)

0. 先判断:你要接的真的是「日志」吗

日志流是给人排障看的时间序列文本。下面这些各有专用面,塞进日志流会同时失去两边的能力:

  • 审计(谁在什么时候改了什么,要可追溯、不可篡改、要留存)→ 门户审计面(audit.write),不是日志。
  • 用量/计费(要精确、要对账)→ 平台计量面。日志会被采样、会过期。
  • 给用户看的消息(提醒、待办、通知)→ 门户通知域。
  • 指标与链路(数值时间序列 / trace)→ 同一个服务的 OTLP 入口,形状与日志不同,见 contract.md。

判据:这条记录丢了会不会有人要赔钱或说不清责任。会 → 不是日志。

1. 三个前提(不成立时,每一步都会以「看起来成功」的方式失败)

  1. 平台把「日志与监控」登记为平台基础服务应用,且默认授予里含 observability.ingest。 这是平台管理员在门户控制台做的一次登记。没有它:你换令牌拿到 HTTP 200、scope 字段却是空的,直到第一次写入才 403。——第一天接日志的团队九成栽在这里,先查它。
  2. 你的 App 在目标租户已安装且可用。 令牌里的 azp 绑着你的 App key,签发时会检查「这个租户装没装你」。没装 ⇒ APP_NOT_INSTALLED。语义是对的:你没在这个租户里跑,就不该有这个租户的日志。
  3. 你有自己的服务账号凭据。 平台部署你的服务时注入 <PREFIX>_SA_CLIENT_ID / <PREFIX>_SA_CLIENT_SECRET(<PREFIX> = 你的 appKey 大写、- 换 _),另有 API_BASE_URL(门户内部源,换令牌用)与 PORTAL_BASE_URL(门户公开源,/svc/... 用)。 与门户同仓的后端没有「平台注入」这一步,同名的键在部署环境的 env 文件里——键名与用法完全一样,下文不再分叉。

不要为日志新建一套密钥,也不要把 observability.ingest 写进自己 manifest 的 serviceScopes:那是别人命名空间的 scope,写进去反而要求你把对方列进依赖;这条 scope 由平台在签发那一刻并进来,manifest 一个字都不用改。

2. 选写入身份

短期服务态令牌长期访问密钥(xsak_ 开头)
怎么拿POST {API_BASE_URL}/api/tokens/service,HTTP Basic = client_id:secret,body grant_type=client_credentials + tenant_id + scope门户控制台 › 服务账号 › 访问密钥(平台管理员签;租户自助建的服务账号同理)
有效期分钟级,要缓存 + 到期前续可无期限
谁用你的应用代码自己打点采集器进程(fluent-bit 这类不会自己换令牌的)
代价每次要一次 HTTP明文长期存在盘上,泄漏面大 ⇒ 只给 observability.ingest 这一条 scope,别多给

拿短期令牌时把它缓存到 expires_in - 10s,别每条日志换一次令牌——那是把日志量乘进了门户的负载。

3. 写入

POST {PORTAL_BASE_URL}/svc/observability/v1/ingest/<stream>
Authorization: Bearer <服务令牌 或 xsak_…>
Content-Type: application/json

[ {…记录1…}, {…记录2…} ]      ← 一次一批,别一条一个请求

成功判据要看两层:HTTP 200 且 响应体 code:200 且 status[] 里 failed:0。只看 HTTP 状态码会把「收下了但没落库」当成功——这正是「写入一直 200、界面永远查不到」的来源。

⚠️ 采集器代发(形态 A/B)时你根本看不到这个响应:采集器只在自己的日志里留一行 HTTP status=200,failed>0 那种「收下了没落库」它不会告诉你,401/403 也只是按重试上限丢掉——采集器不报错 ≠ 接通了。所以这两种形态的验收落在 §8 冒烟那一条 curl(那里你拿得到响应体)加上界面真的搜到;排障时再临时打开采集器的响应打印(见 collectors.md)。

流名:你写 <stream>,实际落在 app_<你的 appKey>_<stream>——前缀由令牌里的 azp 决定,你写不进别人的命名空间,也别在 <stream> 里再重复一遍自己的 key(会得到 app_foo_foo_console)。一个 App 按用途分流即可:console(进程标准输出)、access、job。

4. 字段口径(跨 App 一致,界面和排障才有共同语言)

字段必要性说明
message必备正文,一条一行
level强烈建议info / warn / error;产生方给的最准,采集器只能猜
service强烈建议进程/子服务名,一个 App 有多个进程时靠它分
host建议实例/主机名,多副本时靠它分
fd采集器填stdout / stderr

时间:落库的时间轴是到达时间,日志自带的时间只是一个普通字段。要让时间轴等于事件时间,就在记录里带上 _timestamp(微秒 epoch),并先用一条冒烟记录验证这个版本认不认——别假设。

级别:如果你的日志是文本行、由采集器搬运,采集器只能靠 stderr 和关键词猜,而 console.warn 一类在多数运行时也走 stderr ⇒ 例行 warn 会整批被判成 error。解法是产生方把级别写进正文(例如正文尾部的 JSON 里 "lvl":"error"),采集器优先取它。判定次序与可直接抄的实现见 collectors.md。

永远不要写进日志:令牌 / 密钥 / 密码、完整 query string(授权码与令牌常在里面)、用户个人信息与租户业务正文、大 blob。排障要的是能定位的 ID,不是内容本身——日志会被转发、留存、被平台管理员看到。

5. 选采集形态

形态适合代价
A 容器 stdout → 日志驱动 → 采集器容器化交付、应用零改动多一个采集器进程
B 采集器 tail 日志文件进程管理器把 stdout 写成文件的部署要处理轮转
C 应用内直接批量 POST想要结构化字段与精确级别、日志量小自己管批量/重试/背压

共性的四条(三种形态都适用,任一漏掉都会在生产上现形):批量发、异步发(不许在请求热路径上同步等远端)、失败丢弃而不是无限重试(日志不值得拖垮主流程)、多行合并(一个栈是一条记录,不是四十条)。

⚠️ 多行合并按 tag/流分缓冲。多个日志源共用一个 tag 时,A 源的栈会把 B 源随后那行吸进同一条记录——每个源(每个文件 / 每个容器 × stdout|stderr)一个 tag。

配置照抄 references/collectors.md。

6. 查

正常路径是人打开「日志与监控」App 查(需要 observability.read,由租户管理员同意授权)。程序化查询要用户身份的令牌,服务密钥打查询面是 401——先确认你是不是拿错了钥匙,再怀疑数据没进去。

7. 排错矩阵

症状多半是动作
换令牌 200,但响应里 scope 是空的平台没登记基础服务应用 / 默认授予里没有 ingest找平台管理员登记,§1.1
写入 403身份类型不对(拿用户票写)或 scope 里没有 ingest用服务身份;查签出来的 scope
写入 401凭证缺失 / 伪造 / 已撤销 / 已过期重签;轮换过的旧密钥立刻失效
换令牌报 APP_NOT_INSTALLED你的 App 没装在这个租户这是设计,不是 bug;换租户或先装
写入 200,但查不到① 只看了 HTTP 没看 status[];② 流名前缀猜错(是 app_<key>_<stream>);③ 时间范围——时间轴是到达时间按序查这三条
查询报 stream not found这个流一条都没写进去过回到上一行
查询 401、但写入 200拿服务密钥查换用户身份
只看到零星几条时间范围太窄;或你的进程本来就几乎不打日志先确认源头有没有行
一次报错被拆成几十条采集器没配多行合并collectors.md
一条记录里混了两个进程的行多个源共用一个 tag每源一个 tag
界面里只有别的 App 的流你的令牌 azp 不是你以为的那个自省一下令牌

8. 冒烟验收(接完逐条走一遍,都过才算接上)

  1. 换出来的令牌 scope 确实含 observability.ingest(不是空)。
  2. 写一条带标记的记录,响应 HTTP 200 且 status[] 里 failed:0。
  3. 在「日志与监控」App 里按这个标记搜到了它,流名是 app_<你的 key>_<stream>。
  4. 故意打一个多行栈,界面里它是一条记录。
  5. 打一条 error 和一条 info,界面里级别分得开。
  6. 把采集器停掉再起来,不重放历史、也不丢新行。

别做的事

  • 别为日志自建一套网关/存储/密钥体系——这条链路已经存在,重造的那份不会有人运维。
  • 别把审计、用量、通知塞进日志流(§0)。
  • 别在生产刷 debug 级别:写入是计量项(按字节),也会把有用的行淹掉。
  • 别把别的 App 的日志代写进自己的流:azp 决定前缀,代写要么被拒,要么污染自己的命名空间。

コメント