@obsunified/collector 2.0.1
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/LICENSE +21 -0
- package/README.md +60 -0
- package/dist/auth.d.ts +2 -0
- package/dist/auth.js +9 -0
- package/dist/auth.js.map +1 -0
- package/dist/chunk-UGWDOGGH.js +319 -0
- package/dist/chunk-UGWDOGGH.js.map +1 -0
- package/dist/chunk-XKQTVIRE.js +671 -0
- package/dist/chunk-XKQTVIRE.js.map +1 -0
- package/dist/index.d.ts +1197 -0
- package/dist/index.js +17707 -0
- package/dist/index.js.map +1 -0
- package/dist/ingest-auth-HFVO1zIp.d.ts +165 -0
- package/dist/openai-FJYOQ7WO.js +384 -0
- package/dist/openai-FJYOQ7WO.js.map +1 -0
- package/package.json +73 -0
- package/src/ai-parity/ai-parity.acceptance.test.ts +449 -0
- package/src/analyses/derive.ts +642 -0
- package/src/analyses/index.ts +50 -0
- package/src/analyses/investigations.ts +277 -0
- package/src/analyses/tier0.ts +568 -0
- package/src/auth/dashboard-auth.ts +269 -0
- package/src/auth/ingest-auth.ts +147 -0
- package/src/durable-objects/tail-hub.test.ts +32 -0
- package/src/durable-objects/tail-hub.ts +158 -0
- package/src/framework/collector.ts +467 -0
- package/src/framework/env.ts +90 -0
- package/src/framework/logger.ts +81 -0
- package/src/index.ts +211 -0
- package/src/lib/action-aggregates.ts +1031 -0
- package/src/lib/action-identity.ts +106 -0
- package/src/lib/actions-store.ts +397 -0
- package/src/lib/ai-pricing.ts +80 -0
- package/src/lib/ai-store/calls.ts +148 -0
- package/src/lib/ai-store/evaluations.test.ts +47 -0
- package/src/lib/ai-store/evaluations.ts +125 -0
- package/src/lib/ai-store/helpers.ts +37 -0
- package/src/lib/ai-store/retention.ts +17 -0
- package/src/lib/ai-store/sessions.ts +277 -0
- package/src/lib/ai-store/spans.ts +109 -0
- package/src/lib/ai-store/types.ts +112 -0
- package/src/lib/ai-store.ts +76 -0
- package/src/lib/alerts-store.ts +458 -0
- package/src/lib/analyses-runner.ts +439 -0
- package/src/lib/analyses-store.test.ts +243 -0
- package/src/lib/analyses-store.ts +454 -0
- package/src/lib/ask.test.ts +302 -0
- package/src/lib/ask.ts +390 -0
- package/src/lib/blob-store-s3.ts +213 -0
- package/src/lib/blob-store.ts +366 -0
- package/src/lib/eval-cases-store.test.ts +171 -0
- package/src/lib/eval-cases-store.ts +1033 -0
- package/src/lib/evidence-references.test.ts +10 -0
- package/src/lib/evidence-references.ts +684 -0
- package/src/lib/hash.ts +26 -0
- package/src/lib/identity-index/action-lookups.ts +805 -0
- package/src/lib/identity-index/constants.ts +1 -0
- package/src/lib/identity-index/key-lookups.ts +199 -0
- package/src/lib/identity-index/mappers.ts +349 -0
- package/src/lib/identity-index/types.ts +196 -0
- package/src/lib/identity-index/user-lookup.ts +137 -0
- package/src/lib/identity-index.test.ts +624 -0
- package/src/lib/identity-index.ts +85 -0
- package/src/lib/json.ts +39 -0
- package/src/lib/llm.ts +263 -0
- package/src/lib/logs-store.ts +188 -0
- package/src/lib/metrics-store.test.ts +189 -0
- package/src/lib/metrics-store.ts +312 -0
- package/src/lib/narrate-gate.test.ts +260 -0
- package/src/lib/narrate-gate.ts +253 -0
- package/src/lib/openai.test.ts +300 -0
- package/src/lib/openai.ts +512 -0
- package/src/lib/otlp.ts +212 -0
- package/src/lib/parse-pprof.test.ts +64 -0
- package/src/lib/parse-pprof.ts +184 -0
- package/src/lib/payload-capture.ts +22 -0
- package/src/lib/projects-store.ts +207 -0
- package/src/lib/propagation-metric.test.ts +196 -0
- package/src/lib/propagation-metric.ts +175 -0
- package/src/lib/sql-db-dialect.test.ts +22 -0
- package/src/lib/sql-db-postgres.ts +229 -0
- package/src/lib/sql-db.ts +166 -0
- package/src/lib/stage6.test.ts +216 -0
- package/src/lib/store/export.ts +65 -0
- package/src/lib/store/helpers.ts +367 -0
- package/src/lib/store/ingest.ts +68 -0
- package/src/lib/store/issues.ts +158 -0
- package/src/lib/store/overview.ts +127 -0
- package/src/lib/store/retention.ts +10 -0
- package/src/lib/store/service-map.ts +384 -0
- package/src/lib/store/trace-detail.bench.ts +76 -0
- package/src/lib/store/trace-detail.test.ts +410 -0
- package/src/lib/store/trace-detail.ts +427 -0
- package/src/lib/store/trace-queries.test.ts +29 -0
- package/src/lib/store/trace-queries.ts +89 -0
- package/src/lib/store-overview.test.ts +77 -0
- package/src/lib/store.ts +96 -0
- package/src/lib/tail-publisher.ts +64 -0
- package/src/lib/test-utils/mem-sql-db.test.ts +93 -0
- package/src/lib/test-utils/mem-sql-db.ts +86 -0
- package/src/lib/ua-parser.ts +61 -0
- package/src/lib/usage-store.ts +567 -0
- package/src/migrations/001_telemetry_spans.sql +28 -0
- package/src/migrations/002_usage_events.sql +24 -0
- package/src/migrations/003_usage_analytics_columns.sql +13 -0
- package/src/migrations/004_logs.sql +19 -0
- package/src/migrations/005_ai_calls.sql +25 -0
- package/src/migrations/006_user_profiles.sql +11 -0
- package/src/migrations/007_session_replay_metadata.sql +10 -0
- package/src/migrations/008_session_replay_storage_bytes.sql +1 -0
- package/src/migrations/009_projects_and_keys.sql +22 -0
- package/src/migrations/010_signal_project_id.sql +15 -0
- package/src/migrations/011_default_project_seed.sql +3 -0
- package/src/migrations/012_alert_rules.sql +17 -0
- package/src/migrations/013_alert_evaluations.sql +13 -0
- package/src/migrations/014_alert_state.sql +9 -0
- package/src/migrations/015_metrics.sql +58 -0
- package/src/migrations/016_metrics_exp_histogram_summary.sql +5 -0
- package/src/migrations/017_span_dropped_counts.sql +11 -0
- package/src/migrations/018_log_flags.sql +8 -0
- package/src/migrations/019_ai_span_payloads.sql +30 -0
- package/src/migrations/020_ai_span_evaluations.sql +33 -0
- package/src/migrations/021_ai_span_session.sql +13 -0
- package/src/migrations/022_span_log_session_id.sql +13 -0
- package/src/migrations/023_service_map_indexes.sql +32 -0
- package/src/migrations/024_analysis_results.sql +49 -0
- package/src/migrations/025_stage6_autopinning_alerts.sql +38 -0
- package/src/migrations/026_obs_dashboard_project_seed.sql +18 -0
- package/src/migrations/027_identity_propagation.sql +50 -0
- package/src/migrations/028_profile_blobs.sql +57 -0
- package/src/migrations/029_telemetry_sdk_name.sql +20 -0
- package/src/migrations/030_analysis_claim.sql +18 -0
- package/src/migrations/031_agent_action_graph.sql +154 -0
- package/src/migrations/032_payload_capture_flags.sql +8 -0
- package/src/migrations/033_eval_cases.sql +52 -0
- package/src/migrations/034_eval_case_results.sql +23 -0
- package/src/migrations/035_metric_exemplars.sql +33 -0
- package/src/migrations/036_trace_instrumentation_gaps.sql +19 -0
- package/src/migrations/037_eval_runs.sql +37 -0
- package/src/migrations/038_tool_call_evidence.sql +11 -0
- package/src/migrations/039_drop_trace_instrumentation_gaps.sql +11 -0
- package/src/migrations/040_evidence_retrieval_refs.sql +50 -0
- package/src/migrations-postgres/001_telemetry_spans.sql +33 -0
- package/src/migrations-postgres/002_usage_events.sql +24 -0
- package/src/migrations-postgres/003_usage_analytics_columns.sql +13 -0
- package/src/migrations-postgres/004_logs.sql +19 -0
- package/src/migrations-postgres/005_ai_calls.sql +25 -0
- package/src/migrations-postgres/006_user_profiles.sql +11 -0
- package/src/migrations-postgres/007_session_replay_metadata.sql +10 -0
- package/src/migrations-postgres/008_session_replay_storage_bytes.sql +2 -0
- package/src/migrations-postgres/009_projects_and_keys.sql +25 -0
- package/src/migrations-postgres/010_signal_project_id.sql +18 -0
- package/src/migrations-postgres/011_default_project_seed.sql +5 -0
- package/src/migrations-postgres/012_alert_rules.sql +17 -0
- package/src/migrations-postgres/013_alert_evaluations.sql +13 -0
- package/src/migrations-postgres/014_alert_state.sql +9 -0
- package/src/migrations-postgres/015_metrics.sql +47 -0
- package/src/migrations-postgres/016_metrics_exp_histogram_summary.sql +1 -0
- package/src/migrations-postgres/017_span_dropped_counts.sql +4 -0
- package/src/migrations-postgres/018_log_flags.sql +2 -0
- package/src/migrations-postgres/019_ai_span_payloads.sql +18 -0
- package/src/migrations-postgres/020_ai_span_evaluations.sql +21 -0
- package/src/migrations-postgres/021_ai_span_session.sql +7 -0
- package/src/migrations-postgres/022_span_log_session_id.sql +7 -0
- package/src/migrations-postgres/023_service_map_indexes.sql +9 -0
- package/src/migrations-postgres/024_analysis_results.sql +41 -0
- package/src/migrations-postgres/025_stage6_autopinning_alerts.sql +17 -0
- package/src/migrations-postgres/026_obs_dashboard_project_seed.sql +14 -0
- package/src/migrations-postgres/027_identity_propagation.sql +27 -0
- package/src/migrations-postgres/028_profile_blobs.sql +48 -0
- package/src/migrations-postgres/029_telemetry_sdk_name.sql +5 -0
- package/src/migrations-postgres/030_analysis_claim.sql +5 -0
- package/src/migrations-postgres/031_agent_action_graph.sql +190 -0
- package/src/migrations-postgres/032_payload_capture_flags.sql +8 -0
- package/src/migrations-postgres/033_eval_cases.sql +55 -0
- package/src/migrations-postgres/034_eval_case_results.sql +26 -0
- package/src/migrations-postgres/035_metric_exemplars.sql +31 -0
- package/src/migrations-postgres/036_trace_instrumentation_gaps.sql +19 -0
- package/src/migrations-postgres/037_eval_runs.sql +40 -0
- package/src/migrations-postgres/038_tool_call_evidence.sql +11 -0
- package/src/migrations-postgres/039_drop_trace_instrumentation_gaps.sql +11 -0
- package/src/migrations-postgres/040_evidence_retrieval_refs.sql +47 -0
- package/src/migrations-postgres/README.md +40 -0
- package/src/otlp/acceptance.test.ts +373 -0
- package/src/otlp/decode/body.ts +127 -0
- package/src/otlp/decode/logs.ts +96 -0
- package/src/otlp/decode/metrics.ts +402 -0
- package/src/otlp/decode/traces.ts +92 -0
- package/src/otlp/decode/values.ts +150 -0
- package/src/otlp/decode.test.ts +800 -0
- package/src/otlp/decode.ts +11 -0
- package/src/otlp/gen/opentelemetry/proto/collector/logs/v1/logs_service_pb.ts +132 -0
- package/src/otlp/gen/opentelemetry/proto/collector/metrics/v1/metrics_service_pb.ts +158 -0
- package/src/otlp/gen/opentelemetry/proto/collector/trace/v1/trace_service_pb.ts +158 -0
- package/src/otlp/gen/opentelemetry/proto/common/v1/common_pb.ts +289 -0
- package/src/otlp/gen/opentelemetry/proto/logs/v1/logs_pb.ts +397 -0
- package/src/otlp/gen/opentelemetry/proto/metrics/v1/metrics_pb.ts +1329 -0
- package/src/otlp/gen/opentelemetry/proto/resource/v1/resource_pb.ts +86 -0
- package/src/otlp/gen/opentelemetry/proto/trace/v1/trace_pb.ts +710 -0
- package/src/otlp/response.test.ts +158 -0
- package/src/otlp/response.ts +129 -0
- package/src/plugins/_context.ts +15 -0
- package/src/plugins/action-graph-processor/enrichers.ts +45 -0
- package/src/plugins/action-graph-processor/redaction.ts +84 -0
- package/src/plugins/action-graph-processor.test.ts +1023 -0
- package/src/plugins/action-graph-processor.ts +1172 -0
- package/src/plugins/action-routes.test.ts +716 -0
- package/src/plugins/action-routes.ts +343 -0
- package/src/plugins/ai-receiver.ts +254 -0
- package/src/plugins/ai-span-payloads-processor.test.ts +90 -0
- package/src/plugins/ai-span-payloads-processor.ts +180 -0
- package/src/plugins/alerts-evaluator.test.ts +91 -0
- package/src/plugins/alerts-evaluator.ts +328 -0
- package/src/plugins/alerts-routes.ts +101 -0
- package/src/plugins/analyses-routes.ts +138 -0
- package/src/plugins/ask-routes.ts +182 -0
- package/src/plugins/bot-filter.ts +19 -0
- package/src/plugins/connected-routes/manifest.ts +531 -0
- package/src/plugins/connected-routes.test.ts +1791 -0
- package/src/plugins/connected-routes.ts +1103 -0
- package/src/plugins/dashboard-routes.ts +41 -0
- package/src/plugins/default-span-enrichment.ts +92 -0
- package/src/plugins/eval-cases-routes.test.ts +391 -0
- package/src/plugins/eval-cases-routes.ts +529 -0
- package/src/plugins/evidence-retrieval-routes.benchmark.test.ts +245 -0
- package/src/plugins/evidence-retrieval-routes.test.ts +932 -0
- package/src/plugins/evidence-retrieval-routes.ts +2179 -0
- package/src/plugins/gen-ai-normalizer.test.ts +680 -0
- package/src/plugins/gen-ai-normalizer.ts +302 -0
- package/src/plugins/identity-receiver.ts +96 -0
- package/src/plugins/issue-enrichment.ts +132 -0
- package/src/plugins/issue-insights.ts +81 -0
- package/src/plugins/logs-receiver.ts +160 -0
- package/src/plugins/metrics-receiver.ts +91 -0
- package/src/plugins/onboarding-routes.ts +54 -0
- package/src/plugins/otlp-receiver.ts +124 -0
- package/src/plugins/platform-routes.ts +165 -0
- package/src/plugins/profile-routes.ts +468 -0
- package/src/plugins/projects-routes.ts +92 -0
- package/src/plugins/query-routes.ts +220 -0
- package/src/plugins/redaction-processor.ts +132 -0
- package/src/plugins/replay-query-routes.test.ts +38 -0
- package/src/plugins/replay-query-routes.ts +199 -0
- package/src/plugins/replay-receiver.ts +81 -0
- package/src/plugins/tail-routes.test.ts +47 -0
- package/src/plugins/tail-routes.ts +42 -0
- package/src/plugins/timeline-routes.ts +244 -0
- package/src/plugins/ua-enrichment.ts +27 -0
- package/src/plugins/usage-normalization.ts +46 -0
- package/src/plugins/usage-privacy.ts +55 -0
- package/src/plugins/usage-query-routes.ts +102 -0
- package/src/plugins/usage-receiver.ts +129 -0
- package/src/plugins/users-query-routes.ts +78 -0
- package/src/plugins/utm-enrichment.ts +36 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { fromBinary, fromJson } from "@bufbuild/protobuf";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { ExportLogsServiceResponseSchema } from "./gen/opentelemetry/proto/collector/logs/v1/logs_service_pb.js";
|
|
4
|
+
import { ExportMetricsServiceResponseSchema } from "./gen/opentelemetry/proto/collector/metrics/v1/metrics_service_pb.js";
|
|
5
|
+
import { ExportTraceServiceResponseSchema } from "./gen/opentelemetry/proto/collector/trace/v1/trace_service_pb.js";
|
|
6
|
+
import {
|
|
7
|
+
logsResponse,
|
|
8
|
+
metricsResponse,
|
|
9
|
+
otlpRetryableError,
|
|
10
|
+
traceResponse,
|
|
11
|
+
} from "./response";
|
|
12
|
+
|
|
13
|
+
const mockContext = () => {
|
|
14
|
+
type Header = string | Record<string, string>;
|
|
15
|
+
let capturedStatus: number | undefined;
|
|
16
|
+
let capturedHeaders: Record<string, string> | undefined;
|
|
17
|
+
let capturedBody: BodyInit | undefined;
|
|
18
|
+
const ctx = {
|
|
19
|
+
body: (body: BodyInit, status?: number, headers?: Header) => {
|
|
20
|
+
capturedBody = body;
|
|
21
|
+
capturedStatus = status;
|
|
22
|
+
capturedHeaders =
|
|
23
|
+
typeof headers === "string" ? undefined : (headers ?? {});
|
|
24
|
+
return new Response(body, {
|
|
25
|
+
status,
|
|
26
|
+
headers: typeof headers === "string" ? undefined : headers,
|
|
27
|
+
});
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
return {
|
|
31
|
+
ctx: ctx as never,
|
|
32
|
+
get status() {
|
|
33
|
+
return capturedStatus;
|
|
34
|
+
},
|
|
35
|
+
get headers() {
|
|
36
|
+
return capturedHeaders;
|
|
37
|
+
},
|
|
38
|
+
get body() {
|
|
39
|
+
return capturedBody;
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
describe("traceResponse", () => {
|
|
45
|
+
it("returns 200 with empty envelope on full success (JSON)", async () => {
|
|
46
|
+
const m = mockContext();
|
|
47
|
+
const res = traceResponse(m.ctx, "json");
|
|
48
|
+
expect(res.status).toBe(200);
|
|
49
|
+
expect(res.headers.get("Content-Type")).toBe("application/json");
|
|
50
|
+
const text = await res.text();
|
|
51
|
+
const parsed = JSON.parse(text);
|
|
52
|
+
// proto-JSON of empty message is {}
|
|
53
|
+
expect(parsed).toEqual({});
|
|
54
|
+
// Must round-trip via the schema
|
|
55
|
+
expect(() =>
|
|
56
|
+
fromJson(ExportTraceServiceResponseSchema, parsed),
|
|
57
|
+
).not.toThrow();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("returns 200 with empty envelope on full success (protobuf)", async () => {
|
|
61
|
+
const m = mockContext();
|
|
62
|
+
const res = traceResponse(m.ctx, "protobuf");
|
|
63
|
+
expect(res.status).toBe(200);
|
|
64
|
+
expect(res.headers.get("Content-Type")).toBe("application/x-protobuf");
|
|
65
|
+
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
66
|
+
const msg = fromBinary(ExportTraceServiceResponseSchema, bytes);
|
|
67
|
+
expect(msg.partialSuccess).toBeUndefined();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("encodes partial_success (JSON) with rejected count", async () => {
|
|
71
|
+
const m = mockContext();
|
|
72
|
+
const res = traceResponse(m.ctx, "json", {
|
|
73
|
+
rejected: 3,
|
|
74
|
+
errorMessage: "too many",
|
|
75
|
+
});
|
|
76
|
+
expect(res.status).toBe(200);
|
|
77
|
+
const json = JSON.parse(await res.text());
|
|
78
|
+
expect(json.partialSuccess.rejectedSpans).toBe("3"); // int64 in proto-JSON = string
|
|
79
|
+
expect(json.partialSuccess.errorMessage).toBe("too many");
|
|
80
|
+
const msg = fromJson(ExportTraceServiceResponseSchema, json);
|
|
81
|
+
expect(msg.partialSuccess?.rejectedSpans).toBe(3n);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("encodes partial_success (protobuf) with rejected count", async () => {
|
|
85
|
+
const m = mockContext();
|
|
86
|
+
const res = traceResponse(m.ctx, "protobuf", {
|
|
87
|
+
rejected: 7,
|
|
88
|
+
errorMessage: "cap",
|
|
89
|
+
});
|
|
90
|
+
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
91
|
+
const msg = fromBinary(ExportTraceServiceResponseSchema, bytes);
|
|
92
|
+
expect(msg.partialSuccess?.rejectedSpans).toBe(7n);
|
|
93
|
+
expect(msg.partialSuccess?.errorMessage).toBe("cap");
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe("logsResponse", () => {
|
|
98
|
+
it("encodes partial_success with rejectedLogRecords", async () => {
|
|
99
|
+
const m = mockContext();
|
|
100
|
+
const res = logsResponse(m.ctx, "json", {
|
|
101
|
+
rejected: 42,
|
|
102
|
+
errorMessage: "over cap",
|
|
103
|
+
});
|
|
104
|
+
const json = JSON.parse(await res.text());
|
|
105
|
+
expect(json.partialSuccess.rejectedLogRecords).toBe("42");
|
|
106
|
+
const msg = fromJson(ExportLogsServiceResponseSchema, json);
|
|
107
|
+
expect(msg.partialSuccess?.rejectedLogRecords).toBe(42n);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("returns empty envelope on full success", async () => {
|
|
111
|
+
const m = mockContext();
|
|
112
|
+
const res = logsResponse(m.ctx, "json");
|
|
113
|
+
expect(JSON.parse(await res.text())).toEqual({});
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("metricsResponse", () => {
|
|
118
|
+
it("encodes partial_success with rejectedDataPoints", async () => {
|
|
119
|
+
const m = mockContext();
|
|
120
|
+
const res = metricsResponse(m.ctx, "json", {
|
|
121
|
+
rejected: 17,
|
|
122
|
+
errorMessage: "series cap",
|
|
123
|
+
});
|
|
124
|
+
const json = JSON.parse(await res.text());
|
|
125
|
+
expect(json.partialSuccess.rejectedDataPoints).toBe("17");
|
|
126
|
+
const msg = fromJson(ExportMetricsServiceResponseSchema, json);
|
|
127
|
+
expect(msg.partialSuccess?.rejectedDataPoints).toBe(17n);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("returns empty envelope on full success (protobuf)", async () => {
|
|
131
|
+
const m = mockContext();
|
|
132
|
+
const res = metricsResponse(m.ctx, "protobuf");
|
|
133
|
+
expect(res.headers.get("Content-Type")).toBe("application/x-protobuf");
|
|
134
|
+
const msg = fromBinary(
|
|
135
|
+
ExportMetricsServiceResponseSchema,
|
|
136
|
+
new Uint8Array(await res.arrayBuffer()),
|
|
137
|
+
);
|
|
138
|
+
expect(msg.partialSuccess).toBeUndefined();
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe("otlpRetryableError", () => {
|
|
143
|
+
it("returns 503 with Retry-After by default", async () => {
|
|
144
|
+
const m = mockContext();
|
|
145
|
+
const res = otlpRetryableError(m.ctx, 503, "storage down");
|
|
146
|
+
expect(res.status).toBe(503);
|
|
147
|
+
expect(res.headers.get("Retry-After")).toBe("5");
|
|
148
|
+
expect(res.headers.get("Content-Type")).toBe("application/json");
|
|
149
|
+
expect(await res.json()).toEqual({ error: "storage down" });
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("returns 429 with a custom Retry-After", async () => {
|
|
153
|
+
const m = mockContext();
|
|
154
|
+
const res = otlpRetryableError(m.ctx, 429, "slow down", 30);
|
|
155
|
+
expect(res.status).toBe(429);
|
|
156
|
+
expect(res.headers.get("Retry-After")).toBe("30");
|
|
157
|
+
});
|
|
158
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OTLP response envelope encoders.
|
|
3
|
+
*
|
|
4
|
+
* Per the OTLP/HTTP spec, successful responses are always `200 OK` — whether
|
|
5
|
+
* fully accepted or partial — with a serialized `Export{Signal}ServiceResponse`
|
|
6
|
+
* body. Wire format mirrors the request (JSON in → JSON out; protobuf in →
|
|
7
|
+
* protobuf out). `partial_success` is set only when records were actually
|
|
8
|
+
* rejected; an empty partial_success block is semantically identical to a
|
|
9
|
+
* fully-accepted response and wastes bytes.
|
|
10
|
+
*
|
|
11
|
+
* See: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/otlp.md#failures
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { create, type DescMessage, toBinary, toJson } from "@bufbuild/protobuf";
|
|
15
|
+
import type { Context } from "hono";
|
|
16
|
+
import type { OtlpWireFormat } from "./decode";
|
|
17
|
+
import {
|
|
18
|
+
ExportLogsPartialSuccessSchema,
|
|
19
|
+
ExportLogsServiceResponseSchema,
|
|
20
|
+
} from "./gen/opentelemetry/proto/collector/logs/v1/logs_service_pb.js";
|
|
21
|
+
import {
|
|
22
|
+
ExportMetricsPartialSuccessSchema,
|
|
23
|
+
ExportMetricsServiceResponseSchema,
|
|
24
|
+
} from "./gen/opentelemetry/proto/collector/metrics/v1/metrics_service_pb.js";
|
|
25
|
+
import {
|
|
26
|
+
ExportTracePartialSuccessSchema,
|
|
27
|
+
ExportTraceServiceResponseSchema,
|
|
28
|
+
} from "./gen/opentelemetry/proto/collector/trace/v1/trace_service_pb.js";
|
|
29
|
+
|
|
30
|
+
export interface PartialSuccess {
|
|
31
|
+
rejected: number;
|
|
32
|
+
errorMessage: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Return a retryable error response per the OTLP/HTTP spec. Sets the
|
|
37
|
+
* `Retry-After` header which OTel SDKs respect for exponential backoff.
|
|
38
|
+
*
|
|
39
|
+
* 429 Too Many Requests — per-client throttling
|
|
40
|
+
* 503 Service Unavailable — server-side overload or transient failure
|
|
41
|
+
*
|
|
42
|
+
* The body is a plain JSON error, not an OTLP envelope — the spec permits
|
|
43
|
+
* any body (or empty) for failure cases. `Retry-After` uses integer seconds.
|
|
44
|
+
*/
|
|
45
|
+
export const otlpRetryableError = (
|
|
46
|
+
c: Context,
|
|
47
|
+
status: 429 | 503,
|
|
48
|
+
message: string,
|
|
49
|
+
retryAfterSec = 5,
|
|
50
|
+
) =>
|
|
51
|
+
c.body(JSON.stringify({ error: message }), status, {
|
|
52
|
+
"Content-Type": "application/json",
|
|
53
|
+
"Retry-After": String(retryAfterSec),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
export const traceResponse = (
|
|
57
|
+
c: Context,
|
|
58
|
+
wireFormat: OtlpWireFormat,
|
|
59
|
+
partial?: PartialSuccess,
|
|
60
|
+
) => {
|
|
61
|
+
const msg = create(
|
|
62
|
+
ExportTraceServiceResponseSchema,
|
|
63
|
+
partial
|
|
64
|
+
? {
|
|
65
|
+
partialSuccess: create(ExportTracePartialSuccessSchema, {
|
|
66
|
+
rejectedSpans: BigInt(partial.rejected),
|
|
67
|
+
errorMessage: partial.errorMessage,
|
|
68
|
+
}),
|
|
69
|
+
}
|
|
70
|
+
: {},
|
|
71
|
+
);
|
|
72
|
+
return encode(c, wireFormat, ExportTraceServiceResponseSchema, msg);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const logsResponse = (
|
|
76
|
+
c: Context,
|
|
77
|
+
wireFormat: OtlpWireFormat,
|
|
78
|
+
partial?: PartialSuccess,
|
|
79
|
+
) => {
|
|
80
|
+
const msg = create(
|
|
81
|
+
ExportLogsServiceResponseSchema,
|
|
82
|
+
partial
|
|
83
|
+
? {
|
|
84
|
+
partialSuccess: create(ExportLogsPartialSuccessSchema, {
|
|
85
|
+
rejectedLogRecords: BigInt(partial.rejected),
|
|
86
|
+
errorMessage: partial.errorMessage,
|
|
87
|
+
}),
|
|
88
|
+
}
|
|
89
|
+
: {},
|
|
90
|
+
);
|
|
91
|
+
return encode(c, wireFormat, ExportLogsServiceResponseSchema, msg);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const metricsResponse = (
|
|
95
|
+
c: Context,
|
|
96
|
+
wireFormat: OtlpWireFormat,
|
|
97
|
+
partial?: PartialSuccess,
|
|
98
|
+
) => {
|
|
99
|
+
const msg = create(
|
|
100
|
+
ExportMetricsServiceResponseSchema,
|
|
101
|
+
partial
|
|
102
|
+
? {
|
|
103
|
+
partialSuccess: create(ExportMetricsPartialSuccessSchema, {
|
|
104
|
+
rejectedDataPoints: BigInt(partial.rejected),
|
|
105
|
+
errorMessage: partial.errorMessage,
|
|
106
|
+
}),
|
|
107
|
+
}
|
|
108
|
+
: {},
|
|
109
|
+
);
|
|
110
|
+
return encode(c, wireFormat, ExportMetricsServiceResponseSchema, msg);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const encode = <T extends DescMessage>(
|
|
114
|
+
c: Context,
|
|
115
|
+
wireFormat: OtlpWireFormat,
|
|
116
|
+
schema: T,
|
|
117
|
+
msg: Parameters<typeof toBinary<T>>[1],
|
|
118
|
+
) => {
|
|
119
|
+
if (wireFormat === "protobuf") {
|
|
120
|
+
const bytes = toBinary(schema, msg);
|
|
121
|
+
return c.body(bytes, 200, {
|
|
122
|
+
"Content-Type": "application/x-protobuf",
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
const json = toJson(schema, msg);
|
|
126
|
+
return c.body(JSON.stringify(json), 200, {
|
|
127
|
+
"Content-Type": "application/json",
|
|
128
|
+
});
|
|
129
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract the project id set by ingest-auth or dashboard-auth middleware.
|
|
3
|
+
* Returns 'default' when no middleware has attached one (e.g. unauth dev).
|
|
4
|
+
*
|
|
5
|
+
* Uses a loose Context typing so plugins don't need to re-declare `Variables`
|
|
6
|
+
* on every route registration. The underlying value is always a string set by
|
|
7
|
+
* the auth middlewares.
|
|
8
|
+
*/
|
|
9
|
+
interface ProjectContext {
|
|
10
|
+
get(key: "projectId"): string | undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function getProjectId(c: ProjectContext): string {
|
|
14
|
+
return (c.get("projectId") as string | undefined) || "default";
|
|
15
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { StoredSpan } from "@obsunified/types";
|
|
2
|
+
|
|
3
|
+
export interface ActionEnricherPlugin {
|
|
4
|
+
name: string;
|
|
5
|
+
enrichActionRecord?(
|
|
6
|
+
record: Record<string, unknown>,
|
|
7
|
+
span: StoredSpan,
|
|
8
|
+
attributes: Record<string, unknown>,
|
|
9
|
+
): void | Promise<void>;
|
|
10
|
+
enrichAgentRunRecord?(
|
|
11
|
+
record: Record<string, unknown>,
|
|
12
|
+
span: StoredSpan,
|
|
13
|
+
attributes: Record<string, unknown>,
|
|
14
|
+
): void | Promise<void>;
|
|
15
|
+
enrichToolCallRecord?(
|
|
16
|
+
record: Record<string, unknown>,
|
|
17
|
+
span: StoredSpan,
|
|
18
|
+
attributes: Record<string, unknown>,
|
|
19
|
+
): void | Promise<void>;
|
|
20
|
+
enrichRetrievalRecord?(
|
|
21
|
+
record: Record<string, unknown>,
|
|
22
|
+
span: StoredSpan,
|
|
23
|
+
attributes: Record<string, unknown>,
|
|
24
|
+
): void | Promise<void>;
|
|
25
|
+
enrichEvalRecord?(
|
|
26
|
+
record: Record<string, unknown>,
|
|
27
|
+
span: StoredSpan,
|
|
28
|
+
attributes: Record<string, unknown>,
|
|
29
|
+
): void | Promise<void>;
|
|
30
|
+
enrichArtifactRecord?(
|
|
31
|
+
record: Record<string, unknown>,
|
|
32
|
+
span: StoredSpan,
|
|
33
|
+
attributes: Record<string, unknown>,
|
|
34
|
+
): void | Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const enricherPlugins: ActionEnricherPlugin[] = [];
|
|
38
|
+
|
|
39
|
+
export function registerActionEnricherPlugin(plugin: ActionEnricherPlugin) {
|
|
40
|
+
enricherPlugins.push(plugin);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function clearActionEnricherPlugins() {
|
|
44
|
+
enricherPlugins.length = 0;
|
|
45
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export interface RedactionContext {
|
|
2
|
+
projectId: string;
|
|
3
|
+
actionId: string;
|
|
4
|
+
traceId: string;
|
|
5
|
+
spanId: string;
|
|
6
|
+
kind: "tool_call" | "retrieval" | "eval" | "artifact" | "agent_run";
|
|
7
|
+
fieldName: "args" | "result" | "query" | "documents" | "content";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface PayloadRedactorPlugin {
|
|
11
|
+
name: string;
|
|
12
|
+
redact(
|
|
13
|
+
value: unknown,
|
|
14
|
+
context: RedactionContext,
|
|
15
|
+
): unknown | Promise<unknown> | undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const redactionPlugins: PayloadRedactorPlugin[] = [];
|
|
19
|
+
|
|
20
|
+
export function registerRedactionPlugin(plugin: PayloadRedactorPlugin) {
|
|
21
|
+
redactionPlugins.push(plugin);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function clearRedactionPlugins() {
|
|
25
|
+
redactionPlugins.length = 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Default redactor that performs sensitive key scrubbing.
|
|
29
|
+
const DEFAULT_REDACT_KEYS = new Set([
|
|
30
|
+
"authorization",
|
|
31
|
+
"cookie",
|
|
32
|
+
"set-cookie",
|
|
33
|
+
"password",
|
|
34
|
+
"passwd",
|
|
35
|
+
"secret",
|
|
36
|
+
"token",
|
|
37
|
+
"api-key",
|
|
38
|
+
"x-api-key",
|
|
39
|
+
"email",
|
|
40
|
+
"enduser.id",
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
function shouldRedactKey(key: string): boolean {
|
|
44
|
+
const normalized = key.toLowerCase();
|
|
45
|
+
if (DEFAULT_REDACT_KEYS.has(normalized)) return true;
|
|
46
|
+
for (const k of DEFAULT_REDACT_KEYS) {
|
|
47
|
+
if (normalized.endsWith(k)) return true;
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function redactObj(val: unknown): unknown {
|
|
53
|
+
if (Array.isArray(val)) {
|
|
54
|
+
return val.map(redactObj);
|
|
55
|
+
}
|
|
56
|
+
if (val && typeof val === "object") {
|
|
57
|
+
const nextEntries = Object.entries(val).map(([key, nestedValue]) => {
|
|
58
|
+
if (shouldRedactKey(key)) {
|
|
59
|
+
return [key, "[REDACTED]"] as const;
|
|
60
|
+
}
|
|
61
|
+
return [key, redactObj(nestedValue)] as const;
|
|
62
|
+
});
|
|
63
|
+
return Object.fromEntries(nextEntries);
|
|
64
|
+
}
|
|
65
|
+
return val;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function runRedaction(
|
|
69
|
+
value: unknown,
|
|
70
|
+
context: RedactionContext,
|
|
71
|
+
): Promise<unknown> {
|
|
72
|
+
for (const plugin of redactionPlugins) {
|
|
73
|
+
try {
|
|
74
|
+
const res = await plugin.redact(value, context);
|
|
75
|
+
if (res !== undefined) {
|
|
76
|
+
return res;
|
|
77
|
+
}
|
|
78
|
+
} catch (err) {
|
|
79
|
+
console.error(`[redaction-plugin:${plugin.name}] failed`, err);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return redactObj(value);
|
|
84
|
+
}
|