pi-langfuse 1.5.13 → 1.5.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -29
- package/README_CN.md +44 -0
- package/package.json +1 -1
- package/src/capture-policy.ts +14 -2
- package/src/commands.ts +2 -1
- package/src/handlers/agent.ts +3 -2
- package/src/langfuse.ts +6 -1
- package/src/source-metadata.ts +53 -140
- package/types/langfuse-runtime-shims.d.ts +1 -1
package/README.md
CHANGED
|
@@ -108,8 +108,11 @@ export LANGFUSE_CAPTURE_OUTPUTS=true
|
|
|
108
108
|
export LANGFUSE_CAPTURE_TOOL_IO=false
|
|
109
109
|
export LANGFUSE_CAPTURE_SYSTEM_PROMPT=false
|
|
110
110
|
export LANGFUSE_CAPTURE_CWD=false
|
|
111
|
+
export LANGFUSE_CAPTURE_SOURCE_METADATA=false
|
|
111
112
|
```
|
|
112
113
|
|
|
114
|
+
Source metadata remains off in every preset unless `LANGFUSE_CAPTURE_SOURCE_METADATA=true` is set explicitly.
|
|
115
|
+
|
|
113
116
|
All captured payloads are redacted before upload. The extension masks common API keys, bearer tokens, passwords, cookies, private keys, Langfuse keys, GitHub/npm/AWS-style tokens, and local absolute paths.
|
|
114
117
|
|
|
115
118
|
### Payload limits
|
|
@@ -198,50 +201,44 @@ The package also includes a Langfuse CLI skill, so Langfuse data can be queried
|
|
|
198
201
|
|
|
199
202
|
## Source Metadata
|
|
200
203
|
|
|
201
|
-
|
|
204
|
+
Repository source capture is independent of the privacy presets and disabled by default. Enable it only after deciding that commit identity is appropriate for the Langfuse project:
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
export LANGFUSE_CAPTURE_SOURCE_METADATA=true
|
|
208
|
+
```
|
|
202
209
|
|
|
203
|
-
For Git
|
|
210
|
+
For a Git worktree, the extension records only revision state:
|
|
204
211
|
|
|
205
212
|
```json
|
|
206
213
|
{
|
|
207
214
|
"source_type": "git-repo",
|
|
208
|
-
"
|
|
209
|
-
"
|
|
210
|
-
"
|
|
211
|
-
"repo_root_name": "repo",
|
|
212
|
-
"git_branch": "main",
|
|
213
|
-
"git_commit": "abc123",
|
|
214
|
-
"git_remote_host": "github.com",
|
|
215
|
-
"git_remote_path": "owner/repo",
|
|
215
|
+
"vcs.ref.head.revision": "0123456789abcdef...",
|
|
216
|
+
"git_detached": "false",
|
|
217
|
+
"git_dirty": "false",
|
|
216
218
|
"metadata_source": "git-detection"
|
|
217
219
|
}
|
|
218
220
|
```
|
|
219
221
|
|
|
220
|
-
|
|
222
|
+
The revision is the full `HEAD` commit. Dirty state includes tracked changes and untracked files, but never their paths or contents. Detached state is reported without a branch or tag name. Git remotes, URLs, credentials, usernames, branches, absolute paths, and repository names are never inspected or uploaded by this collector.
|
|
221
223
|
|
|
222
|
-
|
|
224
|
+
When capture is off, the collector does not invoke Git and reports `source_type: "disabled"`. Non-Git directories report `non-git`; a missing or unusable Git executable and incomplete Git state report `unavailable`.
|
|
223
225
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
environment
|
|
232
|
-
observability_owner
|
|
226
|
+
Use native Langfuse and OpenTelemetry settings for deployment identity and explicit operator-owned overrides instead of repository files:
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
export LANGFUSE_RELEASE="1.2.3"
|
|
230
|
+
export LANGFUSE_TRACING_ENVIRONMENT="production"
|
|
231
|
+
export OTEL_SERVICE_NAME="pi-agent"
|
|
232
|
+
export OTEL_RESOURCE_ATTRIBUTES="service.version=1.2.3,vcs.repository.name=public-repo"
|
|
233
233
|
```
|
|
234
234
|
|
|
235
|
-
|
|
235
|
+
`LANGFUSE_RELEASE` and `LANGFUSE_TRACING_ENVIRONMENT` keep their Langfuse semantics. `OTEL_SERVICE_NAME` and `OTEL_RESOURCE_ATTRIBUTES` are loaded as process-scoped OpenTelemetry resource attributes; values such as `vcs.repository.name` apply to every session sharing the runtime, may identify private source, and require a runtime restart to change.
|
|
236
236
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
"metadata_source": "non-git"
|
|
241
|
-
}
|
|
242
|
-
```
|
|
237
|
+
### Compatibility and rollback
|
|
238
|
+
|
|
239
|
+
Earlier versions emitted `git_commit`, branch, remote, owner, repository, and repo-local `.pi-langfuse.metadata.json` values by default. New traces use `vcs.ref.head.revision` and stop reading that file. Update dashboards before enabling source capture; historical traces are unchanged.
|
|
243
240
|
|
|
244
|
-
|
|
241
|
+
Unset `LANGFUSE_CAPTURE_SOURCE_METADATA` to stop collection immediately. Pin `pi-langfuse@1.5.12` only if the old schema is required during migration; doing so also restores its broader default source disclosure.
|
|
245
242
|
|
|
246
243
|
## Troubleshooting
|
|
247
244
|
|
package/README_CN.md
CHANGED
|
@@ -108,8 +108,11 @@ export LANGFUSE_CAPTURE_OUTPUTS=true
|
|
|
108
108
|
export LANGFUSE_CAPTURE_TOOL_IO=false
|
|
109
109
|
export LANGFUSE_CAPTURE_SYSTEM_PROMPT=false
|
|
110
110
|
export LANGFUSE_CAPTURE_CWD=false
|
|
111
|
+
export LANGFUSE_CAPTURE_SOURCE_METADATA=false
|
|
111
112
|
```
|
|
112
113
|
|
|
114
|
+
所有隐私预设默认都关闭源码元数据;只有显式设置 `LANGFUSE_CAPTURE_SOURCE_METADATA=true` 才会启用。
|
|
115
|
+
|
|
113
116
|
所有被采集的负载在上传前仍会脱敏。扩展会隐藏常见 API key、Bearer token、密码、Cookie、私钥、Langfuse key、GitHub/npm/AWS 风格 token,并对本地绝对路径做 hash。
|
|
114
117
|
|
|
115
118
|
### 方式 3:持久化 `config.json`
|
|
@@ -175,6 +178,47 @@ pi list
|
|
|
175
178
|
/pi-langfuse-langfuse <查询内容>
|
|
176
179
|
```
|
|
177
180
|
|
|
181
|
+
## 源码元数据
|
|
182
|
+
|
|
183
|
+
仓库源码采集独立于隐私预设,默认关闭。只有在确认提交标识适合写入当前 Langfuse 项目后才启用:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
export LANGFUSE_CAPTURE_SOURCE_METADATA=true
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
对于 Git 工作树,扩展只记录修订状态:
|
|
190
|
+
|
|
191
|
+
```json
|
|
192
|
+
{
|
|
193
|
+
"source_type": "git-repo",
|
|
194
|
+
"vcs.ref.head.revision": "0123456789abcdef...",
|
|
195
|
+
"git_detached": "false",
|
|
196
|
+
"git_dirty": "false",
|
|
197
|
+
"metadata_source": "git-detection"
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
修订值为完整的 `HEAD` 提交。dirty 状态包含已跟踪变更和未跟踪文件,但绝不包含路径或内容。detached 状态不会附带分支名或标签名。此采集器不会检查或上传 Git remote、URL、凭据、用户名、分支、绝对路径或仓库名。
|
|
202
|
+
|
|
203
|
+
关闭采集时,扩展不会调用 Git,并报告 `source_type: "disabled"`。非 Git 目录报告 `non-git`;Git 不可用或仓库状态不完整时报告 `unavailable`。
|
|
204
|
+
|
|
205
|
+
部署标识和显式覆盖应使用 Langfuse 与 OpenTelemetry 原生配置,而不是仓库文件:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
export LANGFUSE_RELEASE="1.2.3"
|
|
209
|
+
export LANGFUSE_TRACING_ENVIRONMENT="production"
|
|
210
|
+
export OTEL_SERVICE_NAME="pi-agent"
|
|
211
|
+
export OTEL_RESOURCE_ATTRIBUTES="service.version=1.2.3,vcs.repository.name=public-repo"
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
`LANGFUSE_RELEASE` 与 `LANGFUSE_TRACING_ENVIRONMENT` 保持 Langfuse 原生语义。`OTEL_SERVICE_NAME` 和 `OTEL_RESOURCE_ATTRIBUTES` 会作为进程级 OpenTelemetry resource attributes 加载;`vcs.repository.name` 等值会应用到共享同一 runtime 的所有会话,可能暴露私有源码身份,而且修改后需要重启 runtime。
|
|
215
|
+
|
|
216
|
+
### 兼容与回滚
|
|
217
|
+
|
|
218
|
+
旧版本默认发送 `git_commit`、分支、remote、owner、仓库名及 `.pi-langfuse.metadata.json` 中的值。新 trace 改用 `vcs.ref.head.revision`,并停止读取该文件。启用源码采集前应更新 dashboard;历史 trace 不受影响。
|
|
219
|
+
|
|
220
|
+
取消设置 `LANGFUSE_CAPTURE_SOURCE_METADATA` 可立即停止采集。迁移期间只有在必须保留旧 schema 时才固定到 `pi-langfuse@1.5.12`;这样也会恢复旧版本更宽泛的默认源码披露。
|
|
221
|
+
|
|
178
222
|
## 故障排除
|
|
179
223
|
|
|
180
224
|
### 没有看到 trace
|
package/package.json
CHANGED
package/src/capture-policy.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { redactValue } from "./redaction.js";
|
|
1
|
+
import { hashPath, redactValue } from "./redaction.js";
|
|
2
2
|
|
|
3
3
|
export interface CapturePolicy {
|
|
4
4
|
readonly captureInputs: boolean;
|
|
@@ -6,6 +6,7 @@ export interface CapturePolicy {
|
|
|
6
6
|
readonly captureToolIo: boolean;
|
|
7
7
|
readonly captureSystemPrompt: boolean;
|
|
8
8
|
readonly captureCwd: boolean;
|
|
9
|
+
readonly captureSourceMetadata: boolean;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export type PrivacyPreset = "metadata-only" | "prompts-only" | "conversations" | "full-debug";
|
|
@@ -36,6 +37,7 @@ const PRESETS: Record<PrivacyPreset, CapturePolicy> = {
|
|
|
36
37
|
captureToolIo: false,
|
|
37
38
|
captureSystemPrompt: false,
|
|
38
39
|
captureCwd: false,
|
|
40
|
+
captureSourceMetadata: false,
|
|
39
41
|
},
|
|
40
42
|
"prompts-only": {
|
|
41
43
|
captureInputs: true,
|
|
@@ -43,6 +45,7 @@ const PRESETS: Record<PrivacyPreset, CapturePolicy> = {
|
|
|
43
45
|
captureToolIo: false,
|
|
44
46
|
captureSystemPrompt: false,
|
|
45
47
|
captureCwd: false,
|
|
48
|
+
captureSourceMetadata: false,
|
|
46
49
|
},
|
|
47
50
|
conversations: {
|
|
48
51
|
captureInputs: true,
|
|
@@ -50,6 +53,7 @@ const PRESETS: Record<PrivacyPreset, CapturePolicy> = {
|
|
|
50
53
|
captureToolIo: false,
|
|
51
54
|
captureSystemPrompt: false,
|
|
52
55
|
captureCwd: false,
|
|
56
|
+
captureSourceMetadata: false,
|
|
53
57
|
},
|
|
54
58
|
"full-debug": {
|
|
55
59
|
captureInputs: true,
|
|
@@ -57,6 +61,7 @@ const PRESETS: Record<PrivacyPreset, CapturePolicy> = {
|
|
|
57
61
|
captureToolIo: true,
|
|
58
62
|
captureSystemPrompt: true,
|
|
59
63
|
captureCwd: true,
|
|
64
|
+
captureSourceMetadata: false,
|
|
60
65
|
},
|
|
61
66
|
};
|
|
62
67
|
|
|
@@ -66,6 +71,7 @@ const FLAG_TO_FIELD = {
|
|
|
66
71
|
LANGFUSE_CAPTURE_TOOL_IO: "captureToolIo",
|
|
67
72
|
LANGFUSE_CAPTURE_SYSTEM_PROMPT: "captureSystemPrompt",
|
|
68
73
|
LANGFUSE_CAPTURE_CWD: "captureCwd",
|
|
74
|
+
LANGFUSE_CAPTURE_SOURCE_METADATA: "captureSourceMetadata",
|
|
69
75
|
} as const;
|
|
70
76
|
|
|
71
77
|
function parseFlag(value: string | undefined): boolean | undefined {
|
|
@@ -105,7 +111,13 @@ function redactMetadata(metadata: Record<string, unknown> | undefined, policy: C
|
|
|
105
111
|
|
|
106
112
|
const output: Record<string, unknown> = {};
|
|
107
113
|
for (const [key, value] of Object.entries(metadata)) {
|
|
108
|
-
if (key === "cwd"
|
|
114
|
+
if (key === "cwd") {
|
|
115
|
+
if (!policy.captureCwd) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
output[key] = typeof value === "string" && /^(?:\/|[A-Za-z]:[\\/]|\\\\)/.test(value)
|
|
119
|
+
? hashPath(value)
|
|
120
|
+
: redactValue(value);
|
|
109
121
|
continue;
|
|
110
122
|
}
|
|
111
123
|
output[key] = redactValue(value);
|
package/src/commands.ts
CHANGED
|
@@ -75,7 +75,7 @@ function isPrivacyPreset(value: string | undefined): value is PrivacyPreset {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
function inferPreset(policy: CapturePolicy): PrivacyPreset | "custom" {
|
|
78
|
-
const entries: Array<[PrivacyPreset, CapturePolicy]> = [
|
|
78
|
+
const entries: Array<[PrivacyPreset, Omit<CapturePolicy, "captureSourceMetadata">]> = [
|
|
79
79
|
[
|
|
80
80
|
"metadata-only",
|
|
81
81
|
{
|
|
@@ -139,6 +139,7 @@ function describePolicy(policy: CapturePolicy) {
|
|
|
139
139
|
`captureToolIo: ${policy.captureToolIo}`,
|
|
140
140
|
`captureSystemPrompt: ${policy.captureSystemPrompt}`,
|
|
141
141
|
`captureCwd: ${policy.captureCwd}`,
|
|
142
|
+
`captureSourceMetadata: ${policy.captureSourceMetadata}`,
|
|
142
143
|
].join("\n");
|
|
143
144
|
}
|
|
144
145
|
|
package/src/handlers/agent.ts
CHANGED
|
@@ -59,7 +59,8 @@ export async function startAgentRun(event: Record<string, unknown>, ctx: any) {
|
|
|
59
59
|
images: event.images,
|
|
60
60
|
context: event.context ?? event.attachments,
|
|
61
61
|
});
|
|
62
|
-
const
|
|
62
|
+
const capturePolicy = getCapturePolicy();
|
|
63
|
+
const sourceMetadata = collectSourceMetadata(cwd, capturePolicy.captureSourceMetadata);
|
|
63
64
|
const captured = applyCapturePolicy(
|
|
64
65
|
{
|
|
65
66
|
input: rawPromptInput,
|
|
@@ -71,7 +72,7 @@ export async function startAgentRun(event: Record<string, unknown>, ctx: any) {
|
|
|
71
72
|
sessionId: state.currentSessionId || undefined,
|
|
72
73
|
},
|
|
73
74
|
},
|
|
74
|
-
|
|
75
|
+
capturePolicy,
|
|
75
76
|
);
|
|
76
77
|
|
|
77
78
|
state.agentState = {
|
package/src/langfuse.ts
CHANGED
|
@@ -660,6 +660,7 @@ export async function getRuntime(): Promise<LangfuseRuntime> {
|
|
|
660
660
|
if (!runtime) {
|
|
661
661
|
const [
|
|
662
662
|
{ BasicTracerProvider },
|
|
663
|
+
{ resources },
|
|
663
664
|
{ context },
|
|
664
665
|
{ AsyncHooksContextManager },
|
|
665
666
|
{ LangfuseSpanProcessor },
|
|
@@ -667,6 +668,7 @@ export async function getRuntime(): Promise<LangfuseRuntime> {
|
|
|
667
668
|
{ LangfuseClient },
|
|
668
669
|
] = await Promise.all([
|
|
669
670
|
import("@opentelemetry/sdk-trace-base"),
|
|
671
|
+
import("@opentelemetry/sdk-node"),
|
|
670
672
|
import("@opentelemetry/api"),
|
|
671
673
|
import("@opentelemetry/context-async-hooks"),
|
|
672
674
|
import("@langfuse/otel"),
|
|
@@ -692,7 +694,10 @@ export async function getRuntime(): Promise<LangfuseRuntime> {
|
|
|
692
694
|
secretKey: state.config.secretKey,
|
|
693
695
|
baseUrl: state.config.host,
|
|
694
696
|
});
|
|
695
|
-
const
|
|
697
|
+
const resource = resources.defaultResource().merge(
|
|
698
|
+
resources.detectResources({ detectors: [resources.envDetector] }),
|
|
699
|
+
);
|
|
700
|
+
const tracerProvider = new BasicTracerProvider({ resource, spanProcessors: [spanProcessor] });
|
|
696
701
|
tracing.setLangfuseTracerProvider(tracerProvider);
|
|
697
702
|
|
|
698
703
|
runtime = {
|
package/src/source-metadata.ts
CHANGED
|
@@ -1,160 +1,73 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import { basename, dirname, join } from "node:path";
|
|
3
1
|
import { execFileSync } from "node:child_process";
|
|
4
2
|
|
|
5
3
|
export type SourceMetadata = Record<string, string>;
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"source_type",
|
|
12
|
-
"service_name",
|
|
13
|
-
"project_slug",
|
|
14
|
-
"environment",
|
|
15
|
-
"observability_owner",
|
|
16
|
-
]);
|
|
5
|
+
type GitResult =
|
|
6
|
+
| { status: "ok"; value: string }
|
|
7
|
+
| { status: "failed" }
|
|
8
|
+
| { status: "unavailable" };
|
|
17
9
|
|
|
18
|
-
function
|
|
10
|
+
function sourceStatus(status: "disabled" | "non-git" | "unavailable"): SourceMetadata {
|
|
19
11
|
return {
|
|
20
|
-
source_type:
|
|
21
|
-
metadata_source:
|
|
12
|
+
source_type: status,
|
|
13
|
+
metadata_source: status,
|
|
22
14
|
};
|
|
23
15
|
}
|
|
24
16
|
|
|
25
|
-
function runGit(cwd: string, args: string[]):
|
|
26
|
-
return execFileSync("git", args, {
|
|
27
|
-
cwd,
|
|
28
|
-
encoding: "utf8",
|
|
29
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
30
|
-
timeout: 2000,
|
|
31
|
-
}).trim();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function firstLine(value: string): string {
|
|
35
|
-
return value.split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? "";
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function sanitizeGitRemote(remoteUrl: string): Partial<Pick<SourceMetadata, "git_remote_host" | "git_remote_path">> {
|
|
39
|
-
const raw = firstLine(remoteUrl).replace(/\.git$/i, "");
|
|
40
|
-
if (!raw) {
|
|
41
|
-
return {};
|
|
42
|
-
}
|
|
43
|
-
|
|
17
|
+
function runGit(cwd: string, args: string[], env: NodeJS.ProcessEnv): GitResult {
|
|
44
18
|
try {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function deriveIdentity(remotePath: string | undefined): Partial<Pick<SourceMetadata, "repo_identity" | "repo_owner" | "repo_name">> {
|
|
63
|
-
if (!remotePath) {
|
|
64
|
-
return {};
|
|
65
|
-
}
|
|
66
|
-
const parts = remotePath.split("/").filter(Boolean);
|
|
67
|
-
if (parts.length < 2) {
|
|
68
|
-
return {};
|
|
69
|
-
}
|
|
70
|
-
const repoName = parts[parts.length - 1];
|
|
71
|
-
const owner = parts[parts.length - 2];
|
|
72
|
-
if (!repoName || !owner || repoName.includes("/")) {
|
|
73
|
-
return {};
|
|
19
|
+
return {
|
|
20
|
+
status: "ok",
|
|
21
|
+
value: execFileSync("git", args, {
|
|
22
|
+
cwd,
|
|
23
|
+
encoding: "utf8",
|
|
24
|
+
env,
|
|
25
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
26
|
+
timeout: 2000,
|
|
27
|
+
}).trim(),
|
|
28
|
+
};
|
|
29
|
+
} catch (error) {
|
|
30
|
+
const failure = error as NodeJS.ErrnoException & { status?: number | null };
|
|
31
|
+
return typeof failure.status === "number"
|
|
32
|
+
? { status: "failed" }
|
|
33
|
+
: { status: "unavailable" };
|
|
74
34
|
}
|
|
75
|
-
return {
|
|
76
|
-
repo_identity: `${owner}/${repoName}`,
|
|
77
|
-
repo_owner: owner,
|
|
78
|
-
repo_name: repoName,
|
|
79
|
-
};
|
|
80
35
|
}
|
|
81
36
|
|
|
82
|
-
function
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
if (current === root) {
|
|
91
|
-
break;
|
|
92
|
-
}
|
|
93
|
-
const parent = dirname(current);
|
|
94
|
-
if (parent === current) {
|
|
95
|
-
break;
|
|
96
|
-
}
|
|
97
|
-
current = parent;
|
|
37
|
+
export function collectSourceMetadata(
|
|
38
|
+
cwd: string,
|
|
39
|
+
enabled: boolean,
|
|
40
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
41
|
+
): SourceMetadata {
|
|
42
|
+
if (!enabled) {
|
|
43
|
+
return sourceStatus("disabled");
|
|
98
44
|
}
|
|
99
|
-
return undefined;
|
|
100
|
-
}
|
|
101
45
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return {};
|
|
46
|
+
const inside = runGit(cwd, ["rev-parse", "--is-inside-work-tree"], env);
|
|
47
|
+
if (inside.status === "unavailable") {
|
|
48
|
+
return sourceStatus("unavailable");
|
|
106
49
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const output: SourceMetadata = {};
|
|
110
|
-
for (const [key, value] of Object.entries(parsed)) {
|
|
111
|
-
if (OVERRIDE_KEYS.has(key) && typeof value === "string" && value.trim()) {
|
|
112
|
-
output[key] = value.trim();
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
if (output.repo_name?.includes("/")) {
|
|
116
|
-
delete output.repo_name;
|
|
117
|
-
}
|
|
118
|
-
return output;
|
|
119
|
-
} catch {
|
|
120
|
-
return {};
|
|
50
|
+
if (inside.status !== "ok" || inside.value !== "true") {
|
|
51
|
+
return sourceStatus("non-git");
|
|
121
52
|
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export function collectSourceMetadata(cwd: string): SourceMetadata {
|
|
125
|
-
try {
|
|
126
|
-
const inside = runGit(cwd, ["rev-parse", "--is-inside-work-tree"]);
|
|
127
|
-
if (inside !== "true") {
|
|
128
|
-
return nonGitMetadata();
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const gitRoot = runGit(cwd, ["rev-parse", "--show-toplevel"]);
|
|
132
|
-
const commit = runGit(cwd, ["rev-parse", "HEAD"]);
|
|
133
|
-
const branch = runGit(cwd, ["branch", "--show-current"]);
|
|
134
|
-
const remote = runGit(cwd, ["config", "--get", "remote.origin.url"]);
|
|
135
|
-
const remoteMetadata = sanitizeGitRemote(remote);
|
|
136
|
-
const derivedIdentity = deriveIdentity(remoteMetadata.git_remote_path);
|
|
137
|
-
const overrides = readWhitelistedOverrides(cwd, gitRoot);
|
|
138
|
-
|
|
139
|
-
const metadata: SourceMetadata = {
|
|
140
|
-
source_type: "git-repo",
|
|
141
|
-
repo_root_name: basename(gitRoot),
|
|
142
|
-
...(branch ? { git_branch: branch } : {}),
|
|
143
|
-
git_commit: commit,
|
|
144
|
-
...remoteMetadata,
|
|
145
|
-
...derivedIdentity,
|
|
146
|
-
...overrides,
|
|
147
|
-
metadata_source: Object.keys(overrides).length > 0 ? "repo-file" : "git-detection",
|
|
148
|
-
};
|
|
149
53
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
54
|
+
const revision = runGit(cwd, ["rev-parse", "--verify", "HEAD"], env);
|
|
55
|
+
const ref = runGit(cwd, ["symbolic-ref", "--quiet", "--short", "HEAD"], env);
|
|
56
|
+
const workingTree = runGit(cwd, ["status", "--porcelain=v1", "--untracked-files=normal"], env);
|
|
57
|
+
if (
|
|
58
|
+
revision.status !== "ok" ||
|
|
59
|
+
!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i.test(revision.value) ||
|
|
60
|
+
workingTree.status !== "ok" ||
|
|
61
|
+
ref.status === "unavailable"
|
|
62
|
+
) {
|
|
63
|
+
return sourceStatus("unavailable");
|
|
159
64
|
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
source_type: "git-repo",
|
|
68
|
+
"vcs.ref.head.revision": revision.value,
|
|
69
|
+
git_detached: String(ref.status !== "ok"),
|
|
70
|
+
git_dirty: String(Boolean(workingTree.value)),
|
|
71
|
+
metadata_source: "git-detection",
|
|
72
|
+
};
|
|
160
73
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
declare module "@opentelemetry/sdk-trace-base" {
|
|
2
2
|
export class BasicTracerProvider {
|
|
3
|
-
constructor(options?: { spanProcessors?: unknown[] });
|
|
3
|
+
constructor(options?: { resource?: unknown; spanProcessors?: unknown[] });
|
|
4
4
|
forceFlush?(): Promise<void>;
|
|
5
5
|
shutdown?(): Promise<void>;
|
|
6
6
|
}
|