@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,567 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UsageStore — union of all repos.
|
|
3
|
+
* - A's in-memory aggregation approach (more flexible, handles legacy load time keys)
|
|
4
|
+
* - A's broader error detection (event_type='frontend_error' OR severity='error')
|
|
5
|
+
* - A's multi-key load time parsing (loadTimeMs, load_time_ms, durationMs)
|
|
6
|
+
* - A's purgeExpired()
|
|
7
|
+
* - A's summarizeMap helper
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
UsageBrowserSummary,
|
|
12
|
+
UsageCountrySummary,
|
|
13
|
+
UsageDeviceSummary,
|
|
14
|
+
UsageErrorSummary,
|
|
15
|
+
UsageEventRecord,
|
|
16
|
+
UsageEventRow,
|
|
17
|
+
UsageEventSummary,
|
|
18
|
+
UsageOSSummary,
|
|
19
|
+
UsageOverviewOptions,
|
|
20
|
+
UsageOverviewResponse,
|
|
21
|
+
UsagePageSummary,
|
|
22
|
+
UsageSessionDetailResponse,
|
|
23
|
+
UsageSessionSummary,
|
|
24
|
+
UsageUtmCampaignSummary,
|
|
25
|
+
UsageUtmMediumSummary,
|
|
26
|
+
UsageUtmSourceSummary,
|
|
27
|
+
} from "@obsunified/types";
|
|
28
|
+
|
|
29
|
+
import { parseJsonRecord } from "./json";
|
|
30
|
+
import { dialectFor, type SqlDb } from "./sql-db";
|
|
31
|
+
|
|
32
|
+
const cutoffIso = (hours: number): string =>
|
|
33
|
+
new Date(Date.now() - hours * 60 * 60 * 1000).toISOString();
|
|
34
|
+
|
|
35
|
+
const summarizeMap = <T>(
|
|
36
|
+
map: Map<string, number>,
|
|
37
|
+
build: (value: string, count: number) => T,
|
|
38
|
+
): T[] =>
|
|
39
|
+
Array.from(map.entries())
|
|
40
|
+
.map(([value, count]) => build(value, count))
|
|
41
|
+
.sort((left, right) => {
|
|
42
|
+
const leftCount = (left as { count: number }).count;
|
|
43
|
+
const rightCount = (right as { count: number }).count;
|
|
44
|
+
return rightCount - leftCount;
|
|
45
|
+
})
|
|
46
|
+
.slice(0, 10);
|
|
47
|
+
|
|
48
|
+
const filterRow = (
|
|
49
|
+
row: UsageEventRow,
|
|
50
|
+
options: UsageOverviewOptions,
|
|
51
|
+
cutoff: string,
|
|
52
|
+
): boolean => {
|
|
53
|
+
if (row.occurred_at < cutoff) return false;
|
|
54
|
+
if (options.path && row.page_path !== options.path) return false;
|
|
55
|
+
if (!options.includeAdmin) {
|
|
56
|
+
const context = parseJsonRecord(row.context_json);
|
|
57
|
+
if (context["collector.is_admin_path"] === true) return false;
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const toSessionSummary = (rows: UsageEventRow[]): UsageSessionSummary => {
|
|
63
|
+
const sorted = [...rows].sort((left, right) =>
|
|
64
|
+
left.occurred_at.localeCompare(right.occurred_at),
|
|
65
|
+
);
|
|
66
|
+
const first = sorted[0];
|
|
67
|
+
const last = sorted[sorted.length - 1];
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
sessionId: first.session_id,
|
|
71
|
+
visitorId: first.visitor_id,
|
|
72
|
+
firstSeen: first.occurred_at,
|
|
73
|
+
lastSeen: last.occurred_at,
|
|
74
|
+
eventCount: sorted.length,
|
|
75
|
+
pageViewCount: sorted.filter((row) => row.event_type === "page_view")
|
|
76
|
+
.length,
|
|
77
|
+
errorCount: sorted.filter(
|
|
78
|
+
(row) => row.event_type === "frontend_error" || row.severity === "error",
|
|
79
|
+
).length,
|
|
80
|
+
lastPath: last.page_path,
|
|
81
|
+
referrer: first.referrer,
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export class UsageStore {
|
|
86
|
+
constructor(private readonly db: SqlDb) {}
|
|
87
|
+
|
|
88
|
+
async ingest(
|
|
89
|
+
events: UsageEventRecord[],
|
|
90
|
+
): Promise<{ inserted: number; sessionCount: number }> {
|
|
91
|
+
if (events.length === 0) return { inserted: 0, sessionCount: 0 };
|
|
92
|
+
|
|
93
|
+
const statements = events.map((event) => {
|
|
94
|
+
if (!event.projectId)
|
|
95
|
+
throw new Error("UsageStore.ingest: event.projectId is required");
|
|
96
|
+
return this.db
|
|
97
|
+
.prepare(`
|
|
98
|
+
INSERT OR IGNORE INTO usage_events (
|
|
99
|
+
project_id, event_id, session_id, visitor_id, event_type, event_name,
|
|
100
|
+
page_path, page_title, referrer, severity, source,
|
|
101
|
+
context_json, properties_json, user_agent, occurred_at,
|
|
102
|
+
received_at, expires_at, country, browser, os,
|
|
103
|
+
device_type, is_bot, utm_source, utm_medium, utm_campaign,
|
|
104
|
+
interaction_id
|
|
105
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
106
|
+
`)
|
|
107
|
+
.bind(
|
|
108
|
+
event.projectId,
|
|
109
|
+
event.eventId,
|
|
110
|
+
event.sessionId,
|
|
111
|
+
event.visitorId,
|
|
112
|
+
event.eventType,
|
|
113
|
+
event.eventName,
|
|
114
|
+
event.pagePath,
|
|
115
|
+
event.pageTitle,
|
|
116
|
+
event.referrer,
|
|
117
|
+
event.severity,
|
|
118
|
+
event.source,
|
|
119
|
+
event.contextJson,
|
|
120
|
+
event.propertiesJson,
|
|
121
|
+
event.userAgent,
|
|
122
|
+
event.occurredAt,
|
|
123
|
+
event.receivedAt,
|
|
124
|
+
event.expiresAt,
|
|
125
|
+
event.country,
|
|
126
|
+
event.browser,
|
|
127
|
+
event.os,
|
|
128
|
+
event.deviceType,
|
|
129
|
+
event.isBot ? 1 : 0,
|
|
130
|
+
event.utmSource,
|
|
131
|
+
event.utmMedium,
|
|
132
|
+
event.utmCampaign,
|
|
133
|
+
event.interactionId ?? null,
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
await this.db.batch(statements);
|
|
138
|
+
return {
|
|
139
|
+
inserted: events.length,
|
|
140
|
+
sessionCount: new Set(events.map((event) => event.sessionId)).size,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async getOverview(
|
|
145
|
+
options: UsageOverviewOptions,
|
|
146
|
+
): Promise<UsageOverviewResponse> {
|
|
147
|
+
if (!options.projectId)
|
|
148
|
+
throw new Error("UsageStore.getOverview: projectId is required");
|
|
149
|
+
const cutoff = cutoffIso(options.hours);
|
|
150
|
+
const result = await this.db
|
|
151
|
+
.prepare(`
|
|
152
|
+
SELECT project_id, event_id, session_id, visitor_id, event_type, event_name,
|
|
153
|
+
page_path, page_title, referrer, severity, source,
|
|
154
|
+
context_json, properties_json, user_agent, occurred_at,
|
|
155
|
+
received_at, country, browser, os, device_type, is_bot,
|
|
156
|
+
utm_source, utm_medium, utm_campaign
|
|
157
|
+
FROM usage_events
|
|
158
|
+
WHERE project_id = ? AND occurred_at >= ?
|
|
159
|
+
ORDER BY occurred_at DESC
|
|
160
|
+
LIMIT 10000
|
|
161
|
+
`)
|
|
162
|
+
.bind(options.projectId, cutoff)
|
|
163
|
+
.all<UsageEventRow>();
|
|
164
|
+
|
|
165
|
+
const rows = (result.results ?? []).filter((row) =>
|
|
166
|
+
filterRow(row, options, cutoff),
|
|
167
|
+
);
|
|
168
|
+
const pageMap = new Map<
|
|
169
|
+
string,
|
|
170
|
+
{
|
|
171
|
+
path: string;
|
|
172
|
+
title: string | null;
|
|
173
|
+
views: number;
|
|
174
|
+
sessions: Set<string>;
|
|
175
|
+
loadTimes: number[];
|
|
176
|
+
errorCount: number;
|
|
177
|
+
}
|
|
178
|
+
>();
|
|
179
|
+
const eventMap = new Map<
|
|
180
|
+
string,
|
|
181
|
+
{
|
|
182
|
+
eventName: string;
|
|
183
|
+
eventType: UsageEventSummary["eventType"];
|
|
184
|
+
totalEvents: number;
|
|
185
|
+
sessions: Set<string>;
|
|
186
|
+
}
|
|
187
|
+
>();
|
|
188
|
+
const sessions = new Map<string, UsageEventRow[]>();
|
|
189
|
+
const browsers = new Map<string, number>();
|
|
190
|
+
const operatingSystems = new Map<string, number>();
|
|
191
|
+
const devices = new Map<string, number>();
|
|
192
|
+
const countries = new Map<string, number>();
|
|
193
|
+
const utmSources = new Map<string, number>();
|
|
194
|
+
const utmMediums = new Map<string, number>();
|
|
195
|
+
const utmCampaigns = new Map<string, number>();
|
|
196
|
+
const hourlyPageViews = new Map<string, number>();
|
|
197
|
+
const frontendErrors: UsageErrorSummary[] = [];
|
|
198
|
+
|
|
199
|
+
let pageViews = 0;
|
|
200
|
+
let interactions = 0;
|
|
201
|
+
let frontendErrorCount = 0;
|
|
202
|
+
let botsFiltered = 0;
|
|
203
|
+
|
|
204
|
+
for (const row of rows) {
|
|
205
|
+
if (row.is_bot) botsFiltered += 1;
|
|
206
|
+
|
|
207
|
+
const sessionRows = sessions.get(row.session_id) ?? [];
|
|
208
|
+
sessionRows.push(row);
|
|
209
|
+
sessions.set(row.session_id, sessionRows);
|
|
210
|
+
|
|
211
|
+
const eventKey = `${row.event_type}:${row.event_name}`;
|
|
212
|
+
const eventEntry = eventMap.get(eventKey) ?? {
|
|
213
|
+
eventName: row.event_name,
|
|
214
|
+
eventType: row.event_type,
|
|
215
|
+
totalEvents: 0,
|
|
216
|
+
sessions: new Set<string>(),
|
|
217
|
+
};
|
|
218
|
+
eventEntry.totalEvents += 1;
|
|
219
|
+
eventEntry.sessions.add(row.session_id);
|
|
220
|
+
eventMap.set(eventKey, eventEntry);
|
|
221
|
+
|
|
222
|
+
if (row.browser)
|
|
223
|
+
browsers.set(row.browser, (browsers.get(row.browser) ?? 0) + 1);
|
|
224
|
+
if (row.os)
|
|
225
|
+
operatingSystems.set(row.os, (operatingSystems.get(row.os) ?? 0) + 1);
|
|
226
|
+
if (row.device_type)
|
|
227
|
+
devices.set(row.device_type, (devices.get(row.device_type) ?? 0) + 1);
|
|
228
|
+
if (row.country)
|
|
229
|
+
countries.set(row.country, (countries.get(row.country) ?? 0) + 1);
|
|
230
|
+
if (row.utm_source)
|
|
231
|
+
utmSources.set(
|
|
232
|
+
row.utm_source,
|
|
233
|
+
(utmSources.get(row.utm_source) ?? 0) + 1,
|
|
234
|
+
);
|
|
235
|
+
if (row.utm_medium)
|
|
236
|
+
utmMediums.set(
|
|
237
|
+
row.utm_medium,
|
|
238
|
+
(utmMediums.get(row.utm_medium) ?? 0) + 1,
|
|
239
|
+
);
|
|
240
|
+
if (row.utm_campaign)
|
|
241
|
+
utmCampaigns.set(
|
|
242
|
+
row.utm_campaign,
|
|
243
|
+
(utmCampaigns.get(row.utm_campaign) ?? 0) + 1,
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
if (row.event_type === "page_view") {
|
|
247
|
+
pageViews += 1;
|
|
248
|
+
const hour = `${row.occurred_at.slice(0, 13)}:00:00.000Z`;
|
|
249
|
+
hourlyPageViews.set(hour, (hourlyPageViews.get(hour) ?? 0) + 1);
|
|
250
|
+
|
|
251
|
+
if (row.page_path) {
|
|
252
|
+
const pageEntry = pageMap.get(row.page_path) ?? {
|
|
253
|
+
path: row.page_path,
|
|
254
|
+
title: row.page_title,
|
|
255
|
+
views: 0,
|
|
256
|
+
sessions: new Set<string>(),
|
|
257
|
+
loadTimes: [],
|
|
258
|
+
errorCount: 0,
|
|
259
|
+
};
|
|
260
|
+
pageEntry.views += 1;
|
|
261
|
+
pageEntry.sessions.add(row.session_id);
|
|
262
|
+
if (!pageEntry.title && row.page_title)
|
|
263
|
+
pageEntry.title = row.page_title;
|
|
264
|
+
|
|
265
|
+
// Multi-key load time parsing (from A)
|
|
266
|
+
const properties = parseJsonRecord(row.properties_json);
|
|
267
|
+
const loadTimeValue =
|
|
268
|
+
properties.loadTimeMs ??
|
|
269
|
+
properties.load_time_ms ??
|
|
270
|
+
properties.durationMs;
|
|
271
|
+
const loadTime =
|
|
272
|
+
typeof loadTimeValue === "number"
|
|
273
|
+
? loadTimeValue
|
|
274
|
+
: typeof loadTimeValue === "string"
|
|
275
|
+
? Number(loadTimeValue)
|
|
276
|
+
: NaN;
|
|
277
|
+
if (Number.isFinite(loadTime)) pageEntry.loadTimes.push(loadTime);
|
|
278
|
+
|
|
279
|
+
pageMap.set(row.page_path, pageEntry);
|
|
280
|
+
}
|
|
281
|
+
} else if (row.event_type === "interaction") {
|
|
282
|
+
interactions += 1;
|
|
283
|
+
} else if (row.event_type === "frontend_error") {
|
|
284
|
+
frontendErrorCount += 1;
|
|
285
|
+
const properties = parseJsonRecord(row.properties_json);
|
|
286
|
+
frontendErrors.push({
|
|
287
|
+
eventId: row.event_id,
|
|
288
|
+
sessionId: row.session_id,
|
|
289
|
+
pagePath: row.page_path,
|
|
290
|
+
errorName:
|
|
291
|
+
typeof properties.errorName === "string"
|
|
292
|
+
? properties.errorName
|
|
293
|
+
: null,
|
|
294
|
+
errorMessage:
|
|
295
|
+
typeof properties.errorMessage === "string"
|
|
296
|
+
? properties.errorMessage
|
|
297
|
+
: null,
|
|
298
|
+
component:
|
|
299
|
+
typeof properties.component === "string"
|
|
300
|
+
? properties.component
|
|
301
|
+
: null,
|
|
302
|
+
occurredAt: row.occurred_at,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Broader error detection (from A)
|
|
307
|
+
if (
|
|
308
|
+
row.page_path &&
|
|
309
|
+
(row.event_type === "frontend_error" || row.severity === "error")
|
|
310
|
+
) {
|
|
311
|
+
const pageEntry = pageMap.get(row.page_path) ?? {
|
|
312
|
+
path: row.page_path,
|
|
313
|
+
title: row.page_title,
|
|
314
|
+
views: 0,
|
|
315
|
+
sessions: new Set<string>(),
|
|
316
|
+
loadTimes: [],
|
|
317
|
+
errorCount: 0,
|
|
318
|
+
};
|
|
319
|
+
pageEntry.errorCount += 1;
|
|
320
|
+
pageMap.set(row.page_path, pageEntry);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const pages: UsagePageSummary[] = Array.from(pageMap.values())
|
|
325
|
+
.map((entry) => ({
|
|
326
|
+
path: entry.path,
|
|
327
|
+
title: entry.title,
|
|
328
|
+
views: entry.views,
|
|
329
|
+
uniqueSessions: entry.sessions.size,
|
|
330
|
+
averageLoadTimeMs:
|
|
331
|
+
entry.loadTimes.length > 0
|
|
332
|
+
? Math.round(
|
|
333
|
+
entry.loadTimes.reduce((sum, v) => sum + v, 0) /
|
|
334
|
+
entry.loadTimes.length,
|
|
335
|
+
)
|
|
336
|
+
: 0,
|
|
337
|
+
errorCount: entry.errorCount,
|
|
338
|
+
}))
|
|
339
|
+
.sort(
|
|
340
|
+
(left, right) =>
|
|
341
|
+
right.views - left.views || left.path.localeCompare(right.path),
|
|
342
|
+
)
|
|
343
|
+
.slice(0, options.limit ?? 20);
|
|
344
|
+
|
|
345
|
+
const recentSessions = Array.from(sessions.values())
|
|
346
|
+
.map((sessionRows) => toSessionSummary(sessionRows))
|
|
347
|
+
.sort((left, right) => right.lastSeen.localeCompare(left.lastSeen))
|
|
348
|
+
.slice(0, 20);
|
|
349
|
+
|
|
350
|
+
return {
|
|
351
|
+
summary: {
|
|
352
|
+
totalEvents: rows.length,
|
|
353
|
+
uniqueSessions: sessions.size,
|
|
354
|
+
uniqueVisitors: new Set(rows.map((row) => row.visitor_id)).size,
|
|
355
|
+
pageViews,
|
|
356
|
+
frontendErrors: frontendErrorCount,
|
|
357
|
+
interactions,
|
|
358
|
+
},
|
|
359
|
+
pages,
|
|
360
|
+
events: Array.from(eventMap.values())
|
|
361
|
+
.map((entry) => ({
|
|
362
|
+
eventName: entry.eventName,
|
|
363
|
+
eventType: entry.eventType,
|
|
364
|
+
totalEvents: entry.totalEvents,
|
|
365
|
+
uniqueSessions: entry.sessions.size,
|
|
366
|
+
}))
|
|
367
|
+
.sort(
|
|
368
|
+
(left, right) =>
|
|
369
|
+
right.totalEvents - left.totalEvents ||
|
|
370
|
+
left.eventName.localeCompare(right.eventName),
|
|
371
|
+
)
|
|
372
|
+
.slice(0, 20),
|
|
373
|
+
recentSessions,
|
|
374
|
+
frontendErrors: frontendErrors
|
|
375
|
+
.sort((left, right) => right.occurredAt.localeCompare(left.occurredAt))
|
|
376
|
+
.slice(0, 20),
|
|
377
|
+
browsers: summarizeMap<UsageBrowserSummary>(
|
|
378
|
+
browsers,
|
|
379
|
+
(browser, count) => ({ browser, count }),
|
|
380
|
+
),
|
|
381
|
+
operatingSystems: summarizeMap<UsageOSSummary>(
|
|
382
|
+
operatingSystems,
|
|
383
|
+
(os, count) => ({ os, count }),
|
|
384
|
+
),
|
|
385
|
+
devices: summarizeMap<UsageDeviceSummary>(devices, (device, count) => ({
|
|
386
|
+
device,
|
|
387
|
+
count,
|
|
388
|
+
})),
|
|
389
|
+
countries: summarizeMap<UsageCountrySummary>(
|
|
390
|
+
countries,
|
|
391
|
+
(country, count) => ({ country, count }),
|
|
392
|
+
),
|
|
393
|
+
utmSources: summarizeMap<UsageUtmSourceSummary>(
|
|
394
|
+
utmSources,
|
|
395
|
+
(source, count) => ({ source, count }),
|
|
396
|
+
),
|
|
397
|
+
utmMediums: summarizeMap<UsageUtmMediumSummary>(
|
|
398
|
+
utmMediums,
|
|
399
|
+
(medium, count) => ({ medium, count }),
|
|
400
|
+
),
|
|
401
|
+
utmCampaigns: summarizeMap<UsageUtmCampaignSummary>(
|
|
402
|
+
utmCampaigns,
|
|
403
|
+
(campaign, count) => ({ campaign, count }),
|
|
404
|
+
),
|
|
405
|
+
hourlyPageViews: Array.from(hourlyPageViews.entries())
|
|
406
|
+
.map(([hour, count]) => ({ hour, count }))
|
|
407
|
+
.sort((left, right) => left.hour.localeCompare(right.hour)),
|
|
408
|
+
botsFiltered,
|
|
409
|
+
filters: {
|
|
410
|
+
path: options.path ?? "all",
|
|
411
|
+
includeAdmin: options.includeAdmin ?? false,
|
|
412
|
+
},
|
|
413
|
+
windowHours: options.hours,
|
|
414
|
+
timestamp: new Date().toISOString(),
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
async getSessionDetail(
|
|
419
|
+
sessionId: string,
|
|
420
|
+
projectId: string,
|
|
421
|
+
): Promise<UsageSessionDetailResponse | null> {
|
|
422
|
+
if (!projectId)
|
|
423
|
+
throw new Error("UsageStore.getSessionDetail: projectId is required");
|
|
424
|
+
const result = await this.db
|
|
425
|
+
.prepare(`
|
|
426
|
+
SELECT project_id, event_id, session_id, visitor_id, event_type, event_name,
|
|
427
|
+
page_path, page_title, referrer, severity, source,
|
|
428
|
+
context_json, properties_json, user_agent, occurred_at,
|
|
429
|
+
received_at, country, browser, os, device_type, is_bot,
|
|
430
|
+
utm_source, utm_medium, utm_campaign, interaction_id
|
|
431
|
+
FROM usage_events
|
|
432
|
+
WHERE project_id = ? AND session_id = ?
|
|
433
|
+
ORDER BY occurred_at ASC
|
|
434
|
+
`)
|
|
435
|
+
.bind(projectId, sessionId)
|
|
436
|
+
.all<UsageEventRow>();
|
|
437
|
+
|
|
438
|
+
const rows = result.results ?? [];
|
|
439
|
+
if (rows.length === 0) return null;
|
|
440
|
+
|
|
441
|
+
return {
|
|
442
|
+
session: toSessionSummary(rows),
|
|
443
|
+
events: rows.map((row) => ({
|
|
444
|
+
eventId: row.event_id,
|
|
445
|
+
eventType: row.event_type,
|
|
446
|
+
eventName: row.event_name,
|
|
447
|
+
pagePath: row.page_path,
|
|
448
|
+
pageTitle: row.page_title,
|
|
449
|
+
severity: row.severity ?? "info",
|
|
450
|
+
occurredAt: row.occurred_at,
|
|
451
|
+
properties: parseJsonRecord(row.properties_json),
|
|
452
|
+
context: parseJsonRecord(row.context_json),
|
|
453
|
+
interactionId: row.interaction_id ?? null,
|
|
454
|
+
})),
|
|
455
|
+
timestamp: new Date().toISOString(),
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* List sessions with an optional qualitative filter.
|
|
461
|
+
*
|
|
462
|
+
* Aggregates per session_id in a single query; supports three filters
|
|
463
|
+
* derived from session-level rollups: sessions that errored, sessions
|
|
464
|
+
* with no interactions (engagement drop-off), and sessions with slow
|
|
465
|
+
* page loads.
|
|
466
|
+
*/
|
|
467
|
+
async listSessions(options: {
|
|
468
|
+
projectId: string;
|
|
469
|
+
hours: number;
|
|
470
|
+
filter: "all" | "ended_in_error" | "dropoff" | "slow";
|
|
471
|
+
slowMs?: number;
|
|
472
|
+
limit?: number;
|
|
473
|
+
}): Promise<{
|
|
474
|
+
sessions: Array<
|
|
475
|
+
UsageSessionSummary & {
|
|
476
|
+
interactionCount: number;
|
|
477
|
+
maxLoadTimeMs: number | null;
|
|
478
|
+
}
|
|
479
|
+
>;
|
|
480
|
+
}> {
|
|
481
|
+
if (!options.projectId)
|
|
482
|
+
throw new Error("UsageStore.listSessions: projectId is required");
|
|
483
|
+
const cutoff = cutoffIso(options.hours);
|
|
484
|
+
const dialect = dialectFor(this.db);
|
|
485
|
+
const slowMs = options.slowMs ?? 3000;
|
|
486
|
+
const limit = Math.max(1, Math.min(1000, options.limit ?? 200));
|
|
487
|
+
const loadMsExpr = dialect.jsonText("properties_json", "$.loadTimeMs");
|
|
488
|
+
const loadTimeMsExpr = dialect.jsonText(
|
|
489
|
+
"properties_json",
|
|
490
|
+
"$.load_time_ms",
|
|
491
|
+
);
|
|
492
|
+
const durationMsExpr = dialect.jsonText("properties_json", "$.durationMs");
|
|
493
|
+
|
|
494
|
+
const result = await this.db
|
|
495
|
+
.prepare(
|
|
496
|
+
`SELECT
|
|
497
|
+
session_id,
|
|
498
|
+
MIN(visitor_id) AS visitor_id,
|
|
499
|
+
MIN(occurred_at) AS first_seen,
|
|
500
|
+
MAX(occurred_at) AS last_seen,
|
|
501
|
+
COUNT(*) AS event_count,
|
|
502
|
+
SUM(CASE WHEN event_type = 'page_view' THEN 1 ELSE 0 END) AS page_view_count,
|
|
503
|
+
SUM(CASE WHEN event_type = 'interaction' THEN 1 ELSE 0 END) AS interaction_count,
|
|
504
|
+
SUM(CASE WHEN event_type = 'frontend_error' OR severity = 'error' THEN 1 ELSE 0 END) AS error_count,
|
|
505
|
+
MAX(CASE WHEN event_type = 'page_view' THEN CAST(COALESCE(${loadMsExpr}, ${loadTimeMsExpr}, ${durationMsExpr}) AS REAL) ELSE NULL END) AS max_load_ms,
|
|
506
|
+
(SELECT referrer FROM usage_events ref WHERE ref.project_id = ue.project_id AND ref.session_id = ue.session_id ORDER BY occurred_at ASC LIMIT 1) AS referrer,
|
|
507
|
+
(SELECT page_path FROM usage_events lp WHERE lp.project_id = ue.project_id AND lp.session_id = ue.session_id ORDER BY occurred_at DESC LIMIT 1) AS last_path
|
|
508
|
+
FROM usage_events ue
|
|
509
|
+
WHERE project_id = ? AND received_at >= ?
|
|
510
|
+
GROUP BY session_id
|
|
511
|
+
ORDER BY last_seen DESC
|
|
512
|
+
LIMIT ?`,
|
|
513
|
+
)
|
|
514
|
+
.bind(options.projectId, cutoff, limit * 3)
|
|
515
|
+
.all<{
|
|
516
|
+
session_id: string;
|
|
517
|
+
visitor_id: string;
|
|
518
|
+
first_seen: string;
|
|
519
|
+
last_seen: string;
|
|
520
|
+
event_count: number;
|
|
521
|
+
page_view_count: number;
|
|
522
|
+
interaction_count: number;
|
|
523
|
+
error_count: number;
|
|
524
|
+
max_load_ms: number | null;
|
|
525
|
+
referrer: string | null;
|
|
526
|
+
last_path: string | null;
|
|
527
|
+
}>();
|
|
528
|
+
|
|
529
|
+
const raw = (result.results ?? []).map((r) => ({
|
|
530
|
+
sessionId: r.session_id,
|
|
531
|
+
visitorId: r.visitor_id,
|
|
532
|
+
firstSeen: r.first_seen,
|
|
533
|
+
lastSeen: r.last_seen,
|
|
534
|
+
eventCount: r.event_count,
|
|
535
|
+
pageViewCount: r.page_view_count,
|
|
536
|
+
errorCount: r.error_count,
|
|
537
|
+
lastPath: r.last_path,
|
|
538
|
+
referrer: r.referrer,
|
|
539
|
+
interactionCount: r.interaction_count,
|
|
540
|
+
maxLoadTimeMs: r.max_load_ms,
|
|
541
|
+
}));
|
|
542
|
+
|
|
543
|
+
const filtered = raw.filter((s) => {
|
|
544
|
+
switch (options.filter) {
|
|
545
|
+
case "ended_in_error":
|
|
546
|
+
return s.errorCount > 0;
|
|
547
|
+
case "dropoff":
|
|
548
|
+
return s.pageViewCount >= 1 && s.interactionCount === 0;
|
|
549
|
+
case "slow":
|
|
550
|
+
return s.maxLoadTimeMs !== null && s.maxLoadTimeMs >= slowMs;
|
|
551
|
+
default:
|
|
552
|
+
return true;
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
return { sessions: filtered.slice(0, limit) };
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
async purgeExpired(): Promise<number> {
|
|
560
|
+
const now = new Date().toISOString();
|
|
561
|
+
const result = await this.db
|
|
562
|
+
.prepare("DELETE FROM usage_events WHERE expires_at <= ?")
|
|
563
|
+
.bind(now)
|
|
564
|
+
.run();
|
|
565
|
+
return result.meta?.changes ?? 0;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS telemetry_spans (
|
|
2
|
+
trace_id TEXT NOT NULL,
|
|
3
|
+
span_id TEXT NOT NULL,
|
|
4
|
+
parent_span_id TEXT,
|
|
5
|
+
service_name TEXT,
|
|
6
|
+
scope_name TEXT,
|
|
7
|
+
scope_version TEXT,
|
|
8
|
+
span_name TEXT NOT NULL,
|
|
9
|
+
span_kind INTEGER,
|
|
10
|
+
status_code INTEGER,
|
|
11
|
+
status_message TEXT,
|
|
12
|
+
start_time TEXT NOT NULL,
|
|
13
|
+
end_time TEXT NOT NULL,
|
|
14
|
+
duration_ms REAL NOT NULL,
|
|
15
|
+
attributes_json TEXT,
|
|
16
|
+
resource_attributes_json TEXT,
|
|
17
|
+
events_json TEXT,
|
|
18
|
+
links_json TEXT,
|
|
19
|
+
received_at TEXT NOT NULL,
|
|
20
|
+
expires_at TEXT NOT NULL,
|
|
21
|
+
PRIMARY KEY (trace_id, span_id)
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
CREATE INDEX IF NOT EXISTS idx_spans_received_at ON telemetry_spans (received_at DESC);
|
|
25
|
+
CREATE INDEX IF NOT EXISTS idx_spans_trace_id ON telemetry_spans (trace_id);
|
|
26
|
+
CREATE INDEX IF NOT EXISTS idx_spans_service ON telemetry_spans (service_name);
|
|
27
|
+
CREATE INDEX IF NOT EXISTS idx_spans_status ON telemetry_spans (status_code);
|
|
28
|
+
CREATE INDEX IF NOT EXISTS idx_spans_root ON telemetry_spans (parent_span_id, received_at DESC);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS usage_events (
|
|
2
|
+
event_id TEXT PRIMARY KEY,
|
|
3
|
+
session_id TEXT NOT NULL,
|
|
4
|
+
visitor_id TEXT NOT NULL,
|
|
5
|
+
event_type TEXT NOT NULL,
|
|
6
|
+
event_name TEXT NOT NULL,
|
|
7
|
+
page_path TEXT,
|
|
8
|
+
page_title TEXT,
|
|
9
|
+
referrer TEXT,
|
|
10
|
+
severity TEXT,
|
|
11
|
+
source TEXT,
|
|
12
|
+
context_json TEXT NOT NULL,
|
|
13
|
+
properties_json TEXT NOT NULL,
|
|
14
|
+
user_agent TEXT,
|
|
15
|
+
occurred_at TEXT NOT NULL,
|
|
16
|
+
received_at TEXT NOT NULL,
|
|
17
|
+
expires_at TEXT NOT NULL
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
CREATE INDEX IF NOT EXISTS idx_usage_received ON usage_events (received_at DESC);
|
|
21
|
+
CREATE INDEX IF NOT EXISTS idx_usage_session ON usage_events (session_id);
|
|
22
|
+
CREATE INDEX IF NOT EXISTS idx_usage_visitor ON usage_events (visitor_id);
|
|
23
|
+
CREATE INDEX IF NOT EXISTS idx_usage_path ON usage_events (page_path);
|
|
24
|
+
CREATE INDEX IF NOT EXISTS idx_usage_type ON usage_events (event_type);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
ALTER TABLE usage_events ADD COLUMN country TEXT;
|
|
2
|
+
ALTER TABLE usage_events ADD COLUMN browser TEXT;
|
|
3
|
+
ALTER TABLE usage_events ADD COLUMN os TEXT;
|
|
4
|
+
ALTER TABLE usage_events ADD COLUMN device_type TEXT;
|
|
5
|
+
ALTER TABLE usage_events ADD COLUMN is_bot INTEGER DEFAULT 0;
|
|
6
|
+
ALTER TABLE usage_events ADD COLUMN utm_source TEXT;
|
|
7
|
+
ALTER TABLE usage_events ADD COLUMN utm_medium TEXT;
|
|
8
|
+
ALTER TABLE usage_events ADD COLUMN utm_campaign TEXT;
|
|
9
|
+
|
|
10
|
+
CREATE INDEX IF NOT EXISTS idx_usage_country ON usage_events (country);
|
|
11
|
+
CREATE INDEX IF NOT EXISTS idx_usage_bot ON usage_events (is_bot);
|
|
12
|
+
CREATE INDEX IF NOT EXISTS idx_usage_browser ON usage_events (browser);
|
|
13
|
+
CREATE INDEX IF NOT EXISTS idx_usage_utm ON usage_events (utm_source);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS logs (
|
|
2
|
+
log_id TEXT PRIMARY KEY,
|
|
3
|
+
trace_id TEXT,
|
|
4
|
+
span_id TEXT,
|
|
5
|
+
service_name TEXT,
|
|
6
|
+
severity TEXT NOT NULL,
|
|
7
|
+
severity_number INTEGER NOT NULL,
|
|
8
|
+
logger_name TEXT,
|
|
9
|
+
message TEXT NOT NULL,
|
|
10
|
+
attributes_json TEXT,
|
|
11
|
+
occurred_at TEXT NOT NULL,
|
|
12
|
+
received_at TEXT NOT NULL,
|
|
13
|
+
expires_at TEXT NOT NULL
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
CREATE INDEX IF NOT EXISTS idx_logs_received ON logs (received_at DESC);
|
|
17
|
+
CREATE INDEX IF NOT EXISTS idx_logs_trace ON logs (trace_id);
|
|
18
|
+
CREATE INDEX IF NOT EXISTS idx_logs_service ON logs (service_name);
|
|
19
|
+
CREATE INDEX IF NOT EXISTS idx_logs_severity ON logs (severity_number);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS ai_calls (
|
|
2
|
+
call_id TEXT PRIMARY KEY,
|
|
3
|
+
trace_id TEXT,
|
|
4
|
+
span_id TEXT,
|
|
5
|
+
service_name TEXT,
|
|
6
|
+
model_name TEXT NOT NULL,
|
|
7
|
+
provider TEXT NOT NULL,
|
|
8
|
+
call_type TEXT NOT NULL, -- 'text_completion', 'chat', 'prompt_to_image', 'embedding'
|
|
9
|
+
request_json TEXT, -- The raw prompt/messages or image configuration
|
|
10
|
+
response_json TEXT, -- The raw completion text or image URLs
|
|
11
|
+
prompt_tokens INTEGER,
|
|
12
|
+
completion_tokens INTEGER,
|
|
13
|
+
total_cost_usd REAL,
|
|
14
|
+
latency_ms REAL,
|
|
15
|
+
is_error INTEGER DEFAULT 0,
|
|
16
|
+
error_message TEXT,
|
|
17
|
+
occurred_at TEXT NOT NULL,
|
|
18
|
+
received_at TEXT NOT NULL,
|
|
19
|
+
expires_at TEXT NOT NULL
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
CREATE INDEX IF NOT EXISTS idx_aicalls_received ON ai_calls (received_at DESC);
|
|
23
|
+
CREATE INDEX IF NOT EXISTS idx_aicalls_trace ON ai_calls (trace_id);
|
|
24
|
+
CREATE INDEX IF NOT EXISTS idx_aicalls_service ON ai_calls (service_name);
|
|
25
|
+
CREATE INDEX IF NOT EXISTS idx_aicalls_model ON ai_calls (model_name);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS user_profiles (
|
|
2
|
+
user_id TEXT PRIMARY KEY,
|
|
3
|
+
visitor_id TEXT NOT NULL,
|
|
4
|
+
email TEXT,
|
|
5
|
+
name TEXT,
|
|
6
|
+
properties_json TEXT, -- custom traits (plan_tier, role)
|
|
7
|
+
first_seen_at TEXT NOT NULL,
|
|
8
|
+
last_seen_at TEXT NOT NULL
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
CREATE INDEX IF NOT EXISTS idx_user_visitor ON user_profiles(visitor_id);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS session_replay_metadata (
|
|
2
|
+
session_id TEXT PRIMARY KEY,
|
|
3
|
+
visitor_id TEXT NOT NULL,
|
|
4
|
+
first_chunk_at TEXT NOT NULL,
|
|
5
|
+
last_chunk_at TEXT NOT NULL,
|
|
6
|
+
chunk_count INTEGER DEFAULT 0,
|
|
7
|
+
events_count INTEGER DEFAULT 0
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
CREATE INDEX IF NOT EXISTS idx_replay_visitor ON session_replay_metadata(visitor_id);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE session_replay_metadata ADD COLUMN storage_bytes INTEGER NOT NULL DEFAULT 0;
|