@oneuptime/common 10.5.8 → 10.5.17
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/Models/AnalyticsModels/ExceptionInstance.ts +1 -1
- package/Models/AnalyticsModels/Log.ts +1 -1
- package/Models/AnalyticsModels/Metric.ts +1 -1
- package/Models/AnalyticsModels/Profile.ts +1 -1
- package/Models/AnalyticsModels/ProfileSample.ts +1 -1
- package/Models/AnalyticsModels/Span.ts +1 -1
- package/Models/DatabaseModels/SmsLog.ts +111 -0
- package/Models/DatabaseModels/TelemetryException.ts +46 -34
- package/Models/DatabaseModels/TelemetryUsageBilling.ts +35 -2
- package/Server/API/AIAgentDataAPI.ts +25 -7
- package/Server/API/DashboardAPI.ts +616 -0
- package/Server/API/TelemetryExceptionAPI.ts +6 -2
- package/Server/Infrastructure/Postgres/SchemaMigrations/1780317745887-AddDeliveryTrackingToSmsLog.ts +39 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1780381124553-MigrationName.ts +28 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1780382837019-MigrationName.ts +24 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1780387560604-MigrationName.ts +47 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1780388219225-MigrationName.ts +34 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
- package/Server/Infrastructure/QueueWorker.ts +40 -1
- package/Server/Services/AnalyticsDatabaseService.ts +87 -0
- package/Server/Services/DatabaseService.ts +73 -0
- package/Server/Services/TelemetryExceptionService.ts +24 -49
- package/Server/Services/TelemetryUsageBillingService.ts +289 -166
- package/Server/Types/AnalyticsDatabase/ModelPermission.ts +102 -72
- package/Server/Types/Database/Permissions/OwnedScopePermission.ts +81 -60
- package/Server/Types/Database/Permissions/OwnerTableRegistry.ts +67 -0
- package/Server/Utils/Logger.ts +12 -1
- package/Server/Utils/StartServer.ts +13 -5
- package/Server/Utils/Telemetry/ContextSpanProcessor.ts +48 -0
- package/Server/Utils/Telemetry/SpanUtil.ts +16 -35
- package/Server/Utils/Telemetry/TelemetryContext.ts +190 -0
- package/Server/Utils/Telemetry.ts +18 -2
- package/Types/Database/AccessControl/OwnedThrough.ts +31 -3
- package/Types/SmsStatus.ts +16 -0
- package/Types/Telemetry/ServiceType.ts +10 -0
- package/UI/Components/LogsViewer/LogsViewer.tsx +16 -0
- package/UI/Utils/Project.ts +6 -0
- package/UI/Utils/Telemetry/Telemetry.ts +65 -0
- package/UI/Utils/TelemetryService.ts +150 -0
- package/build/dist/Models/AnalyticsModels/ExceptionInstance.js +1 -1
- package/build/dist/Models/AnalyticsModels/ExceptionInstance.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Log.js +1 -1
- package/build/dist/Models/AnalyticsModels/Log.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Metric.js +1 -1
- package/build/dist/Models/AnalyticsModels/Metric.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Profile.js +1 -1
- package/build/dist/Models/AnalyticsModels/Profile.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/ProfileSample.js +1 -1
- package/build/dist/Models/AnalyticsModels/ProfileSample.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Span.js +1 -1
- package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
- package/build/dist/Models/DatabaseModels/SmsLog.js +112 -0
- package/build/dist/Models/DatabaseModels/SmsLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TelemetryException.js +47 -33
- package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TelemetryUsageBilling.js +36 -2
- package/build/dist/Models/DatabaseModels/TelemetryUsageBilling.js.map +1 -1
- package/build/dist/Server/API/AIAgentDataAPI.js +24 -8
- package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
- package/build/dist/Server/API/DashboardAPI.js +459 -2
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/API/TelemetryExceptionAPI.js +6 -2
- package/build/dist/Server/API/TelemetryExceptionAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780317745887-AddDeliveryTrackingToSmsLog.js +20 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780317745887-AddDeliveryTrackingToSmsLog.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780381124553-MigrationName.js +23 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780381124553-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780382837019-MigrationName.js +19 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780382837019-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780387560604-MigrationName.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780387560604-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780388219225-MigrationName.js +25 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1780388219225-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/QueueWorker.js +31 -1
- package/build/dist/Server/Infrastructure/QueueWorker.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +59 -0
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +62 -0
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryExceptionService.js +16 -41
- package/build/dist/Server/Services/TelemetryExceptionService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryUsageBillingService.js +211 -147
- package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
- package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js +84 -63
- package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/OwnedScopePermission.js +67 -49
- package/build/dist/Server/Types/Database/Permissions/OwnedScopePermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js +51 -0
- package/build/dist/Server/Types/Database/Permissions/OwnerTableRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Logger.js +8 -1
- package/build/dist/Server/Utils/Logger.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +12 -4
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ContextSpanProcessor.js +37 -0
- package/build/dist/Server/Utils/Telemetry/ContextSpanProcessor.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/SpanUtil.js +15 -24
- package/build/dist/Server/Utils/Telemetry/SpanUtil.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryContext.js +124 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryContext.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry.js +12 -1
- package/build/dist/Server/Utils/Telemetry.js.map +1 -1
- package/build/dist/Types/Database/AccessControl/OwnedThrough.js +7 -2
- package/build/dist/Types/Database/AccessControl/OwnedThrough.js.map +1 -1
- package/build/dist/Types/SmsStatus.js +15 -0
- package/build/dist/Types/SmsStatus.js.map +1 -1
- package/build/dist/Types/Telemetry/ServiceType.js +10 -0
- package/build/dist/Types/Telemetry/ServiceType.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +15 -0
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Utils/Project.js +5 -0
- package/build/dist/UI/Utils/Project.js.map +1 -1
- package/build/dist/UI/Utils/Telemetry/Telemetry.js +44 -0
- package/build/dist/UI/Utils/Telemetry/Telemetry.js.map +1 -1
- package/build/dist/UI/Utils/TelemetryService.js +113 -0
- package/build/dist/UI/Utils/TelemetryService.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { DisableTelemetry } from "../../EnvironmentConfig";
|
|
2
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
3
|
+
|
|
4
|
+
export type TelemetryContextAttributeValue =
|
|
5
|
+
| string
|
|
6
|
+
| number
|
|
7
|
+
| boolean
|
|
8
|
+
| undefined;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Canonical set of tenant/business attributes we propagate across a unit of
|
|
12
|
+
* work (an HTTP request, a worker job, a probe check, a cron run). The open
|
|
13
|
+
* index signature allows additional ad-hoc keys, but prefer the named ones so
|
|
14
|
+
* dashboards and queries stay consistent.
|
|
15
|
+
*/
|
|
16
|
+
export interface TelemetryContextAttributes {
|
|
17
|
+
userId?: string | undefined;
|
|
18
|
+
projectId?: string | undefined;
|
|
19
|
+
requestId?: string | undefined;
|
|
20
|
+
incidentId?: string | undefined;
|
|
21
|
+
alertId?: string | undefined;
|
|
22
|
+
monitorId?: string | undefined;
|
|
23
|
+
statusPageId?: string | undefined;
|
|
24
|
+
scheduledMaintenanceId?: string | undefined;
|
|
25
|
+
onCallDutyPolicyId?: string | undefined;
|
|
26
|
+
onCallDutyPolicyScheduleId?: string | undefined;
|
|
27
|
+
incidentEpisodeId?: string | undefined;
|
|
28
|
+
alertEpisodeId?: string | undefined;
|
|
29
|
+
workspaceType?: string | undefined;
|
|
30
|
+
channelId?: string | undefined;
|
|
31
|
+
[key: string]: TelemetryContextAttributeValue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface TelemetryContextStore {
|
|
35
|
+
attributes: Record<string, string | number | boolean>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Ambient, mutable telemetry context backed by AsyncLocalStorage.
|
|
40
|
+
*
|
|
41
|
+
* Why this exists: OpenTelemetry span attributes do NOT propagate from a
|
|
42
|
+
* parent span to its children, and OneUptime sets tenant context (projectId,
|
|
43
|
+
* userId, ...) deep in middleware while the spans that matter are created much
|
|
44
|
+
* further down the call stack. Rather than thread attributes through every
|
|
45
|
+
* function or tag ~1958 `@CaptureSpan` call sites by hand, we keep a small
|
|
46
|
+
* mutable attribute bag scoped to the current unit of work. `ContextSpanProcessor`
|
|
47
|
+
* stamps it onto every span at creation, and `Logger` merges it into every log
|
|
48
|
+
* record — so context flows everywhere automatically.
|
|
49
|
+
*
|
|
50
|
+
* Seed a scope at each entry point with `runWithContext`, then enrich it as
|
|
51
|
+
* more identifiers become known with `setAttributes`.
|
|
52
|
+
*/
|
|
53
|
+
export default class TelemetryContext {
|
|
54
|
+
private static storage: AsyncLocalStorage<TelemetryContextStore> =
|
|
55
|
+
new AsyncLocalStorage<TelemetryContextStore>();
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Identifier keys we look for when seeding context from an arbitrary payload
|
|
59
|
+
* (see {@link pickKnownAttributes}).
|
|
60
|
+
*/
|
|
61
|
+
private static readonly KNOWN_ID_KEYS: Array<string> = [
|
|
62
|
+
"projectId",
|
|
63
|
+
"userId",
|
|
64
|
+
"monitorId",
|
|
65
|
+
"incidentId",
|
|
66
|
+
"alertId",
|
|
67
|
+
"statusPageId",
|
|
68
|
+
"scheduledMaintenanceId",
|
|
69
|
+
"onCallDutyPolicyId",
|
|
70
|
+
"onCallDutyPolicyScheduleId",
|
|
71
|
+
"incidentEpisodeId",
|
|
72
|
+
"alertEpisodeId",
|
|
73
|
+
"workspaceType",
|
|
74
|
+
"channelId",
|
|
75
|
+
"requestId",
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Run `fn` within a fresh telemetry-context scope seeded with `attributes`.
|
|
80
|
+
* Any attributes from an enclosing scope are inherited so nested units of
|
|
81
|
+
* work (e.g. a job spawned while handling a request) keep their context.
|
|
82
|
+
*/
|
|
83
|
+
public static runWithContext<T>(
|
|
84
|
+
attributes: TelemetryContextAttributes,
|
|
85
|
+
fn: () => T,
|
|
86
|
+
): T {
|
|
87
|
+
if (DisableTelemetry) {
|
|
88
|
+
return fn();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const inherited: Record<string, string | number | boolean> =
|
|
92
|
+
this.getAttributes();
|
|
93
|
+
|
|
94
|
+
const store: TelemetryContextStore = {
|
|
95
|
+
attributes: { ...inherited },
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
this.mergeInto(store.attributes, attributes);
|
|
99
|
+
|
|
100
|
+
return this.storage.run(store, fn);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Merge `attributes` into the current scope. No-op when there is no active
|
|
105
|
+
* scope (e.g. code running outside any seeded entry point) or telemetry is
|
|
106
|
+
* disabled, so it is always safe to call.
|
|
107
|
+
*/
|
|
108
|
+
public static setAttributes(attributes: TelemetryContextAttributes): void {
|
|
109
|
+
if (DisableTelemetry) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const store: TelemetryContextStore | undefined = this.storage.getStore();
|
|
114
|
+
|
|
115
|
+
if (!store) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
this.mergeInto(store.attributes, attributes);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Read the attributes for the current scope. Returns an empty object when
|
|
124
|
+
* there is no active scope. The returned object is the live store — treat it
|
|
125
|
+
* as read-only (consumers copy it before mutating).
|
|
126
|
+
*/
|
|
127
|
+
public static getAttributes(): Record<string, string | number | boolean> {
|
|
128
|
+
const store: TelemetryContextStore | undefined = this.storage.getStore();
|
|
129
|
+
|
|
130
|
+
if (!store) {
|
|
131
|
+
return {};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return store.attributes;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Best-effort extraction of known tenant/business identifiers from an
|
|
139
|
+
* arbitrary object (e.g. a queue job's `data` payload), so worker jobs can
|
|
140
|
+
* seed context without every job knowing about telemetry. Values are coerced
|
|
141
|
+
* to strings; unknown/empty values are skipped.
|
|
142
|
+
*/
|
|
143
|
+
public static pickKnownAttributes(data: unknown): TelemetryContextAttributes {
|
|
144
|
+
const attributes: TelemetryContextAttributes = {};
|
|
145
|
+
|
|
146
|
+
if (!data || typeof data !== "object") {
|
|
147
|
+
return attributes;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const record: Record<string, unknown> = data as Record<string, unknown>;
|
|
151
|
+
|
|
152
|
+
for (const key of this.KNOWN_ID_KEYS) {
|
|
153
|
+
const value: unknown = record[key];
|
|
154
|
+
|
|
155
|
+
if (value === undefined || value === null) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (
|
|
160
|
+
typeof value === "string" ||
|
|
161
|
+
typeof value === "number" ||
|
|
162
|
+
typeof value === "boolean"
|
|
163
|
+
) {
|
|
164
|
+
attributes[key] = value;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ObjectID-like values expose a meaningful toString().
|
|
169
|
+
const asString: string = String(value);
|
|
170
|
+
if (asString && asString !== "[object Object]") {
|
|
171
|
+
attributes[key] = asString;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return attributes;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private static mergeInto(
|
|
179
|
+
target: Record<string, string | number | boolean>,
|
|
180
|
+
attributes: TelemetryContextAttributes,
|
|
181
|
+
): void {
|
|
182
|
+
for (const key in attributes) {
|
|
183
|
+
const value: TelemetryContextAttributeValue = attributes[key];
|
|
184
|
+
|
|
185
|
+
if (value !== undefined && value !== null) {
|
|
186
|
+
target[key] = value;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -39,7 +39,11 @@ import {
|
|
|
39
39
|
} from "@opentelemetry/sdk-metrics";
|
|
40
40
|
import type { PushMetricExporter } from "@opentelemetry/sdk-metrics/build/src/export/MetricExporter";
|
|
41
41
|
import * as opentelemetry from "@opentelemetry/sdk-node";
|
|
42
|
-
import {
|
|
42
|
+
import {
|
|
43
|
+
BatchSpanProcessor,
|
|
44
|
+
SpanExporter,
|
|
45
|
+
SpanProcessor,
|
|
46
|
+
} from "@opentelemetry/sdk-trace-base";
|
|
43
47
|
import {
|
|
44
48
|
ATTR_SERVICE_NAME,
|
|
45
49
|
ATTR_SERVICE_VERSION,
|
|
@@ -48,6 +52,7 @@ import URL from "../../Types/API/URL";
|
|
|
48
52
|
import Dictionary from "../../Types/Dictionary";
|
|
49
53
|
import { AppVersion, Env, DisableTelemetry } from "../EnvironmentConfig";
|
|
50
54
|
import logger from "./Logger";
|
|
55
|
+
import ContextSpanProcessor from "./Telemetry/ContextSpanProcessor";
|
|
51
56
|
import RuntimeMetrics from "./Telemetry/RuntimeMetrics";
|
|
52
57
|
|
|
53
58
|
type ResourceWithRawAttributes = LogsResource & {
|
|
@@ -268,10 +273,21 @@ export default class Telemetry {
|
|
|
268
273
|
autoDetectResources: true,
|
|
269
274
|
};
|
|
270
275
|
|
|
276
|
+
/*
|
|
277
|
+
* Always run the ContextSpanProcessor so the ambient TelemetryContext
|
|
278
|
+
* attributes (projectId, userId, monitorId, incidentId, requestId, ...)
|
|
279
|
+
* are stamped onto every span at creation. The BatchSpanProcessor that
|
|
280
|
+
* actually exports spans is added after it, and only when an exporter is
|
|
281
|
+
* configured. (traceExporter is deprecated in favour of spanProcessors.)
|
|
282
|
+
*/
|
|
283
|
+
const spanProcessors: Array<SpanProcessor> = [new ContextSpanProcessor()];
|
|
284
|
+
|
|
271
285
|
if (traceExporter) {
|
|
272
|
-
|
|
286
|
+
spanProcessors.push(new BatchSpanProcessor(traceExporter));
|
|
273
287
|
}
|
|
274
288
|
|
|
289
|
+
nodeSdkConfiguration.spanProcessors = spanProcessors;
|
|
290
|
+
|
|
275
291
|
/*
|
|
276
292
|
* We will skip this becasue we're attachng this metric reader to the meter provider later.
|
|
277
293
|
* if (this.metricReader) {
|
|
@@ -2,11 +2,39 @@ import GenericFunction from "../../GenericFunction";
|
|
|
2
2
|
|
|
3
3
|
export interface OwnedThroughMetadata {
|
|
4
4
|
fkColumn: string;
|
|
5
|
-
|
|
5
|
+
/*
|
|
6
|
+
* The parent resource type(s) whose ownership grants access to this
|
|
7
|
+
* model's rows. Usually one (e.g. Monitor), but a polymorphic FK can
|
|
8
|
+
* inherit ownership from several resource types — e.g. a telemetry
|
|
9
|
+
* row's serviceId may reference a Service, Host, DockerHost or
|
|
10
|
+
* KubernetesCluster, so Owned scope unions the owned ids across all of
|
|
11
|
+
* them.
|
|
12
|
+
*/
|
|
13
|
+
parentModels: Array<GenericFunction>;
|
|
14
|
+
/*
|
|
15
|
+
* When true, rows whose fkColumn equals the tenant (project) id itself
|
|
16
|
+
* are also visible under Owned scope. Telemetry with no owning resource
|
|
17
|
+
* (the unattributed "Unknown" bucket) is tagged with the projectId in
|
|
18
|
+
* place of a resource id; it belongs to the project, not any single
|
|
19
|
+
* owner, so every in-project user with the table permission may see it.
|
|
20
|
+
*/
|
|
21
|
+
includeProjectScope: boolean;
|
|
6
22
|
}
|
|
7
23
|
|
|
8
|
-
export
|
|
24
|
+
export interface OwnedThroughOptions {
|
|
25
|
+
includeProjectScope?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default (
|
|
29
|
+
fkColumn: string,
|
|
30
|
+
parentModel: GenericFunction | Array<GenericFunction>,
|
|
31
|
+
options?: OwnedThroughOptions,
|
|
32
|
+
) => {
|
|
9
33
|
return (ctr: GenericFunction) => {
|
|
10
|
-
ctr.prototype.ownedThrough = {
|
|
34
|
+
ctr.prototype.ownedThrough = {
|
|
35
|
+
fkColumn,
|
|
36
|
+
parentModels: Array.isArray(parentModel) ? parentModel : [parentModel],
|
|
37
|
+
includeProjectScope: options?.includeProjectScope ?? false,
|
|
38
|
+
};
|
|
11
39
|
};
|
|
12
40
|
};
|
package/Types/SmsStatus.ts
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
enum SmsStatus {
|
|
2
|
+
/*
|
|
3
|
+
* Legacy / synchronous statuses.
|
|
4
|
+
* "Success" was historically set the moment Twilio accepted the message (before
|
|
5
|
+
* delivery tracking existed). It is kept for backward compatibility with rows
|
|
6
|
+
* written before delivery receipts were introduced.
|
|
7
|
+
*/
|
|
2
8
|
Success = "Success",
|
|
3
9
|
Error = "Error",
|
|
4
10
|
LowBalance = "Low Balance",
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
* Delivery lifecycle statuses, driven by Twilio status callbacks.
|
|
14
|
+
* Sending -> Sent -> Delivered (terminal) | Undelivered (terminal) | Failed (terminal)
|
|
15
|
+
*/
|
|
16
|
+
Sending = "Sending", // queued / accepted / sending — handed to Twilio, not yet at carrier
|
|
17
|
+
Sent = "Sent", // accepted by the carrier, awaiting a delivery receipt
|
|
18
|
+
Delivered = "Delivered", // carrier confirmed delivery to the handset
|
|
19
|
+
Undelivered = "Undelivered", // carrier could not deliver (e.g. filtered, unreachable)
|
|
20
|
+
Failed = "Failed", // Twilio could not send the message
|
|
5
21
|
}
|
|
6
22
|
|
|
7
23
|
export default SmsStatus;
|
|
@@ -14,6 +14,16 @@ enum ServiceType {
|
|
|
14
14
|
Host = "Host",
|
|
15
15
|
DockerHost = "DockerHost",
|
|
16
16
|
KubernetesCluster = "KubernetesCluster",
|
|
17
|
+
/*
|
|
18
|
+
* Telemetry that arrived without an OTel service.name and with no
|
|
19
|
+
* host / docker / k8s resource signal. Instead of synthesising a
|
|
20
|
+
* placeholder "Unknown Service" Postgres row (which collected every
|
|
21
|
+
* oneuptime.label.* attribute from unrelated sources), the row's
|
|
22
|
+
* `serviceId` slot holds the projectId and no Service row is created.
|
|
23
|
+
* The read side renders these under a synthetic "Unknown Service"
|
|
24
|
+
* bucket.
|
|
25
|
+
*/
|
|
26
|
+
Unknown = "Unknown",
|
|
17
27
|
}
|
|
18
28
|
|
|
19
29
|
export default ServiceType;
|
|
@@ -60,6 +60,8 @@ import { queryStringToFilter } from "../../../Types/Log/LogQueryToFilter";
|
|
|
60
60
|
import RangeStartAndEndDateTime from "../../../Types/Time/RangeStartAndEndDateTime";
|
|
61
61
|
import TimeRange from "../../../Types/Time/TimeRange";
|
|
62
62
|
import { exportLogs, LogExportFormat } from "../../Utils/LogExport";
|
|
63
|
+
import ProjectUtil from "../../Utils/Project";
|
|
64
|
+
import TelemetryServiceUtil from "../../Utils/TelemetryService";
|
|
63
65
|
import ObjectID from "../../../Types/ObjectID";
|
|
64
66
|
import OneUptimeDate from "../../../Types/Date";
|
|
65
67
|
|
|
@@ -460,6 +462,20 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
|
|
|
460
462
|
services[service.id.toString()] = service;
|
|
461
463
|
});
|
|
462
464
|
|
|
465
|
+
/*
|
|
466
|
+
* Logs without a service.name are tagged with the projectId
|
|
467
|
+
* (ServiceType.Unknown) and have no Service row. Register a
|
|
468
|
+
* synthetic "Unknown Service" keyed by the projectId so the
|
|
469
|
+
* serviceId -> name resolution and the serviceId facet render
|
|
470
|
+
* "Unknown Service" instead of a raw id. Harmless when no logs
|
|
471
|
+
* are unattributed — nothing resolves against it.
|
|
472
|
+
*/
|
|
473
|
+
const projectId: ObjectID | null = ProjectUtil.getCurrentProjectId();
|
|
474
|
+
if (projectId) {
|
|
475
|
+
services[projectId.toString()] =
|
|
476
|
+
TelemetryServiceUtil.getUnknownService(projectId);
|
|
477
|
+
}
|
|
478
|
+
|
|
463
479
|
const hostsById: Dictionary<Host> = {};
|
|
464
480
|
hosts.data.forEach((host: Host) => {
|
|
465
481
|
if (!host.id) {
|
package/UI/Utils/Project.ts
CHANGED
|
@@ -12,6 +12,7 @@ import SubscriptionStatus, {
|
|
|
12
12
|
} from "../../Types/Billing/SubscriptionStatus";
|
|
13
13
|
import Navigation from "./Navigation";
|
|
14
14
|
import SessionStorage from "./SessionStorage";
|
|
15
|
+
import Telemetry from "./Telemetry/Telemetry";
|
|
15
16
|
|
|
16
17
|
export default class ProjectUtil {
|
|
17
18
|
public static getCurrentProjectId(): ObjectID | null {
|
|
@@ -107,6 +108,11 @@ export default class ProjectUtil {
|
|
|
107
108
|
}
|
|
108
109
|
LocalStorage.setItem(`project_${currentProjectId}`, project);
|
|
109
110
|
SessionStorage.setItem(`current_project_id`, currentProjectId);
|
|
111
|
+
|
|
112
|
+
// Keep RUM span context in sync with the project being viewed.
|
|
113
|
+
if (currentProjectId) {
|
|
114
|
+
Telemetry.setGlobalAttributes({ projectId: currentProjectId });
|
|
115
|
+
}
|
|
110
116
|
}
|
|
111
117
|
|
|
112
118
|
public static clearCurrentProject(): void {
|
|
@@ -19,7 +19,65 @@ import type { SpanExporter as WebSpanExporter } from "@opentelemetry/sdk-trace-w
|
|
|
19
19
|
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
|
|
20
20
|
import URL from "../../../Types/API/URL";
|
|
21
21
|
|
|
22
|
+
/*
|
|
23
|
+
* Mutable, module-level bag of attributes stamped onto every browser span.
|
|
24
|
+
* Populated by `Telemetry.setGlobalAttributes` once the signed-in user and
|
|
25
|
+
* the project being viewed are known (RUM context).
|
|
26
|
+
*/
|
|
27
|
+
const globalSpanAttributes: { [key: string]: string } = {};
|
|
28
|
+
|
|
29
|
+
interface MutableSpanLike {
|
|
30
|
+
setAttribute(key: string, value: string): unknown;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Stamps the global RUM attributes (userId, projectId, ...) onto each span at
|
|
35
|
+
* creation, mirroring the server-side ContextSpanProcessor.
|
|
36
|
+
*/
|
|
37
|
+
class GlobalAttributeSpanProcessor {
|
|
38
|
+
public onStart(span: MutableSpanLike): void {
|
|
39
|
+
for (const key in globalSpanAttributes) {
|
|
40
|
+
const value: string | undefined = globalSpanAttributes[key];
|
|
41
|
+
|
|
42
|
+
if (value) {
|
|
43
|
+
span.setAttribute(key, value);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public onEnd(): void {
|
|
49
|
+
// no-op
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public forceFlush(): Promise<void> {
|
|
53
|
+
return Promise.resolve();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public shutdown(): Promise<void> {
|
|
57
|
+
return Promise.resolve();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
22
61
|
export default class Telemetry {
|
|
62
|
+
/**
|
|
63
|
+
* Set RUM attributes that should be attached to every browser span from now
|
|
64
|
+
* on (e.g. userId, projectId). Safe to call before or after `init`; empty
|
|
65
|
+
* values are ignored.
|
|
66
|
+
*/
|
|
67
|
+
public static setGlobalAttributes(attributes: {
|
|
68
|
+
userId?: string | undefined;
|
|
69
|
+
projectId?: string | undefined;
|
|
70
|
+
[key: string]: string | undefined;
|
|
71
|
+
}): void {
|
|
72
|
+
for (const key in attributes) {
|
|
73
|
+
const value: string | undefined = attributes[key];
|
|
74
|
+
|
|
75
|
+
if (value) {
|
|
76
|
+
globalSpanAttributes[key] = value;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
23
81
|
public static init(data: { serviceName: string }): void {
|
|
24
82
|
if (DisableTelemetry) {
|
|
25
83
|
return;
|
|
@@ -49,6 +107,13 @@ export default class Telemetry {
|
|
|
49
107
|
|
|
50
108
|
provider.addSpanProcessor(new BatchSpanProcessor(webTraceExporter));
|
|
51
109
|
|
|
110
|
+
// Stamp global RUM attributes (userId, projectId, ...) onto every span.
|
|
111
|
+
provider.addSpanProcessor(
|
|
112
|
+
new GlobalAttributeSpanProcessor() as unknown as Parameters<
|
|
113
|
+
WebTracerProvider["addSpanProcessor"]
|
|
114
|
+
>[0],
|
|
115
|
+
);
|
|
116
|
+
|
|
52
117
|
provider.register({
|
|
53
118
|
contextManager: new ZoneContextManager(),
|
|
54
119
|
});
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import Service from "../../Models/DatabaseModels/Service";
|
|
2
|
+
import { Gray500 } from "../../Types/BrandColors";
|
|
3
|
+
import ObjectID from "../../Types/ObjectID";
|
|
4
|
+
import ServiceType from "../../Types/Telemetry/ServiceType";
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* Telemetry that arrives without an OTel service.name (and with no
|
|
8
|
+
* host / docker / k8s resource signal) is not backed by a Service row.
|
|
9
|
+
* The ingest path tags those analytics rows with the projectId in the
|
|
10
|
+
* `serviceId` column under ServiceType.Unknown (see
|
|
11
|
+
* OtelIngestBaseService.resolveTelemetryResource). The read side has no
|
|
12
|
+
* Service to resolve, so we represent that telemetry with a synthetic,
|
|
13
|
+
* non-persisted Service whose id is the projectId — that way the
|
|
14
|
+
* existing serviceId -> Service lookups in the telemetry views resolve
|
|
15
|
+
* it to a labelled "Unknown Service" entry without any per-view special
|
|
16
|
+
* casing. A real Service._id is never equal to a projectId, so this
|
|
17
|
+
* never collides with a genuine service.
|
|
18
|
+
*/
|
|
19
|
+
export const UNKNOWN_SERVICE_NAME: string = "Unknown Service";
|
|
20
|
+
|
|
21
|
+
/*
|
|
22
|
+
* Result of resolving a telemetry row's polymorphic (serviceId,
|
|
23
|
+
* serviceType) to something renderable. Either a Service (a real
|
|
24
|
+
* OpenTelemetry service, or the synthetic "Unknown Service" for the
|
|
25
|
+
* unattributed bucket) — or a plain `label` for infrastructure resource
|
|
26
|
+
* types (Host / DockerHost / KubernetesCluster) that have no Service row.
|
|
27
|
+
*/
|
|
28
|
+
export interface ResolvedTelemetryResource {
|
|
29
|
+
service?: Service;
|
|
30
|
+
label?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default class TelemetryServiceUtil {
|
|
34
|
+
/*
|
|
35
|
+
* True when a telemetry row's serviceId is the synthetic "Unknown
|
|
36
|
+
* Service" — i.e. it equals the projectId (ServiceType.Unknown). Used
|
|
37
|
+
* to suppress navigation to a per-service detail page that does not
|
|
38
|
+
* exist for unattributed telemetry.
|
|
39
|
+
*/
|
|
40
|
+
public static isUnknownServiceId(
|
|
41
|
+
serviceId: ObjectID | string | null | undefined,
|
|
42
|
+
projectId: ObjectID | null | undefined,
|
|
43
|
+
): boolean {
|
|
44
|
+
if (!serviceId || !projectId) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
return serviceId.toString() === projectId.toString();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/*
|
|
51
|
+
* Build the synthetic Service used to render unattributed telemetry.
|
|
52
|
+
* Not persisted — id is the projectId so that serviceId -> Service
|
|
53
|
+
* lookups (which key on the analytics row's serviceId) resolve to it.
|
|
54
|
+
*/
|
|
55
|
+
public static getUnknownService(projectId: ObjectID): Service {
|
|
56
|
+
const service: Service = new Service();
|
|
57
|
+
service.id = projectId;
|
|
58
|
+
service.name = UNKNOWN_SERVICE_NAME;
|
|
59
|
+
service.serviceColor = Gray500;
|
|
60
|
+
return service;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/*
|
|
64
|
+
* Resolve a telemetry row's polymorphic (serviceId, serviceType) to a
|
|
65
|
+
* renderable resource, given the project's loaded Services. Replaces the
|
|
66
|
+
* old server-side `service` ORM relation on TelemetryException: a real
|
|
67
|
+
* Service resolves from the loaded list, the unattributed bucket resolves
|
|
68
|
+
* to the synthetic "Unknown Service", and Host / DockerHost /
|
|
69
|
+
* KubernetesCluster resolve to a type label (no Service row exists for
|
|
70
|
+
* them). Mirrors how the ClickHouse analytics rows are resolved.
|
|
71
|
+
*/
|
|
72
|
+
public static resolveTelemetryResource(data: {
|
|
73
|
+
serviceId: ObjectID | string | null | undefined;
|
|
74
|
+
serviceType: ServiceType | string | null | undefined;
|
|
75
|
+
services: Array<Service>;
|
|
76
|
+
projectId: ObjectID | null | undefined;
|
|
77
|
+
}): ResolvedTelemetryResource {
|
|
78
|
+
const serviceIdStr: string | undefined = data.serviceId?.toString();
|
|
79
|
+
|
|
80
|
+
// Real Service (OpenTelemetry) — resolve from the loaded list.
|
|
81
|
+
if (serviceIdStr) {
|
|
82
|
+
const found: Service | undefined = data.services.find((s: Service) => {
|
|
83
|
+
return s.id?.toString() === serviceIdStr;
|
|
84
|
+
});
|
|
85
|
+
if (found) {
|
|
86
|
+
return { service: found };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Unattributed (Unknown) bucket — serviceId is the projectId.
|
|
91
|
+
if (
|
|
92
|
+
data.projectId &&
|
|
93
|
+
(data.serviceType === ServiceType.Unknown ||
|
|
94
|
+
this.isUnknownServiceId(data.serviceId, data.projectId))
|
|
95
|
+
) {
|
|
96
|
+
return { service: this.getUnknownService(data.projectId) };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Infrastructure resource types — no Service row; render a type label.
|
|
100
|
+
const typeLabels: Record<string, string> = {
|
|
101
|
+
[ServiceType.Host]: "Host telemetry",
|
|
102
|
+
[ServiceType.DockerHost]: "Docker host telemetry",
|
|
103
|
+
[ServiceType.KubernetesCluster]: "Kubernetes telemetry",
|
|
104
|
+
};
|
|
105
|
+
const label: string | undefined = data.serviceType
|
|
106
|
+
? typeLabels[data.serviceType.toString()]
|
|
107
|
+
: undefined;
|
|
108
|
+
if (label) {
|
|
109
|
+
return { label: label };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return { label: "Unknown" };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/*
|
|
116
|
+
* Append the synthetic "Unknown Service" to a loaded service list, but
|
|
117
|
+
* only when the telemetry in view actually references it (some
|
|
118
|
+
* serviceId equals the projectId). Avoids showing an empty "Unknown
|
|
119
|
+
* Service" entry for projects that always set service.name. Idempotent.
|
|
120
|
+
*/
|
|
121
|
+
public static withUnknownServiceIfReferenced(data: {
|
|
122
|
+
services: Array<Service>;
|
|
123
|
+
referencedServiceIds: Iterable<string>;
|
|
124
|
+
projectId: ObjectID;
|
|
125
|
+
}): Array<Service> {
|
|
126
|
+
const projectIdStr: string = data.projectId.toString();
|
|
127
|
+
|
|
128
|
+
let isReferenced: boolean = false;
|
|
129
|
+
for (const id of data.referencedServiceIds) {
|
|
130
|
+
if (id === projectIdStr) {
|
|
131
|
+
isReferenced = true;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!isReferenced) {
|
|
137
|
+
return data.services;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const alreadyPresent: boolean = data.services.some((service: Service) => {
|
|
141
|
+
return service.id?.toString() === projectIdStr;
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (alreadyPresent) {
|
|
145
|
+
return data.services;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return [...data.services, this.getUnknownService(data.projectId)];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -785,7 +785,7 @@ let ExceptionInstance = class ExceptionInstance extends AnalyticsBaseModel {
|
|
|
785
785
|
};
|
|
786
786
|
ExceptionInstance = __decorate([
|
|
787
787
|
OperationalResource(),
|
|
788
|
-
OwnedThrough("serviceId", Service),
|
|
788
|
+
OwnedThrough("serviceId", Service, { includeProjectScope: true }),
|
|
789
789
|
__metadata("design:paramtypes", [])
|
|
790
790
|
], ExceptionInstance);
|
|
791
791
|
export default ExceptionInstance;
|