@oneuptime/common 10.5.9 → 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/TelemetryException.ts +46 -34
- package/Models/DatabaseModels/TelemetryUsageBilling.ts +35 -2
- package/Server/API/AIAgentDataAPI.ts +25 -7
- package/Server/API/TelemetryExceptionAPI.ts +6 -2
- 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 +8 -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/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/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/TelemetryExceptionAPI.js +6 -2
- package/build/dist/Server/API/TelemetryExceptionAPI.js.map +1 -1
- 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 +8 -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/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 +1 -1
|
@@ -807,6 +807,23 @@ export default class ModelPermission {
|
|
|
807
807
|
}
|
|
808
808
|
}
|
|
809
809
|
|
|
810
|
+
/*
|
|
811
|
+
* Telemetry with no owning resource (the unattributed "Unknown"
|
|
812
|
+
* bucket) is tagged with the projectId in place of a resource id. It
|
|
813
|
+
* belongs to the project, not any owner, so an Owned-scoped user
|
|
814
|
+
* (project-level catch-all access) sees it. Gated on hasOwnedGrant:
|
|
815
|
+
* a purely Labels-scoped user asked for label-matching telemetry, and
|
|
816
|
+
* the unattributed bucket carries no labels, so it stays excluded for
|
|
817
|
+
* them.
|
|
818
|
+
*/
|
|
819
|
+
if (
|
|
820
|
+
hasOwnedGrant &&
|
|
821
|
+
model.ownedThrough.includeProjectScope &&
|
|
822
|
+
props.tenantId
|
|
823
|
+
) {
|
|
824
|
+
allowedResourceIds.add(props.tenantId.toString());
|
|
825
|
+
}
|
|
826
|
+
|
|
810
827
|
const fkColumn: string = model.ownedThrough.fkColumn;
|
|
811
828
|
const idList: Array<string> =
|
|
812
829
|
allowedResourceIds.size > 0
|
|
@@ -841,64 +858,73 @@ export default class ModelPermission {
|
|
|
841
858
|
|
|
842
859
|
const ownerTableRegistry: Map<
|
|
843
860
|
string,
|
|
844
|
-
|
|
845
|
-
|
|
861
|
+
{
|
|
862
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
863
|
+
ownerUserService: any;
|
|
864
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
865
|
+
ownerTeamService: any;
|
|
866
|
+
fkColumn: string;
|
|
867
|
+
canOwnTelemetry?: boolean;
|
|
868
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
869
|
+
modelService?: any;
|
|
870
|
+
}
|
|
846
871
|
> =
|
|
847
872
|
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
848
873
|
require("../Database/Permissions/OwnerTableRegistry").default;
|
|
849
874
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
875
|
+
/*
|
|
876
|
+
* Telemetry serviceId is polymorphic — it can reference any resource
|
|
877
|
+
* type flagged `canOwnTelemetry` in the registry (Service, Monitor,
|
|
878
|
+
* Host, DockerHost, KubernetesCluster). Resolve ownership across all of
|
|
879
|
+
* them so a user who owns any such resource sees its telemetry, not
|
|
880
|
+
* just owned Services. The polymorphic set lives only in the registry
|
|
881
|
+
* (single source of truth); the resolved union is the same for every
|
|
882
|
+
* telemetry analytics model, so the single per-request `ownedIds`
|
|
883
|
+
* cache slot still holds it.
|
|
884
|
+
*/
|
|
885
|
+
for (const entry of ownerTableRegistry.values()) {
|
|
886
|
+
if (!entry.canOwnTelemetry) {
|
|
887
|
+
continue;
|
|
888
|
+
}
|
|
889
|
+
const fkColumn: string = entry.fkColumn;
|
|
863
890
|
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
await serviceEntry.ownerUserService.findBy({
|
|
891
|
+
if (props.userId) {
|
|
892
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
893
|
+
const userOwnedRows: Array<any> = await entry.ownerUserService.findBy({
|
|
868
894
|
query: {
|
|
869
895
|
userId: props.userId,
|
|
870
896
|
...(props.tenantId ? { projectId: props.tenantId } : {}),
|
|
871
897
|
},
|
|
872
|
-
select: {
|
|
898
|
+
select: { [fkColumn]: true },
|
|
873
899
|
props: { isRoot: true },
|
|
874
900
|
skip: 0,
|
|
875
901
|
limit: LIMIT_MAX,
|
|
876
902
|
});
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
903
|
+
for (const row of userOwnedRows) {
|
|
904
|
+
const id: ObjectID | undefined = row[fkColumn];
|
|
905
|
+
if (id) {
|
|
906
|
+
result.add(id.toString());
|
|
907
|
+
}
|
|
881
908
|
}
|
|
882
909
|
}
|
|
883
|
-
}
|
|
884
910
|
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
await serviceEntry.ownerTeamService.findBy({
|
|
911
|
+
if (props.userTeamIds && props.userTeamIds.length > 0) {
|
|
912
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
913
|
+
const teamOwnedRows: Array<any> = await entry.ownerTeamService.findBy({
|
|
889
914
|
query: {
|
|
890
915
|
teamId: QueryHelper.any(props.userTeamIds),
|
|
891
916
|
...(props.tenantId ? { projectId: props.tenantId } : {}),
|
|
892
917
|
},
|
|
893
|
-
select: {
|
|
918
|
+
select: { [fkColumn]: true },
|
|
894
919
|
props: { isRoot: true },
|
|
895
920
|
skip: 0,
|
|
896
921
|
limit: LIMIT_MAX,
|
|
897
922
|
});
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
923
|
+
for (const row of teamOwnedRows) {
|
|
924
|
+
const id: ObjectID | undefined = row[fkColumn];
|
|
925
|
+
if (id) {
|
|
926
|
+
result.add(id.toString());
|
|
927
|
+
}
|
|
902
928
|
}
|
|
903
929
|
}
|
|
904
930
|
}
|
|
@@ -942,50 +968,54 @@ export default class ModelPermission {
|
|
|
942
968
|
return cached;
|
|
943
969
|
}
|
|
944
970
|
|
|
945
|
-
const ServiceService: any =
|
|
946
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
947
|
-
require("../../Services/ServiceService").default;
|
|
948
|
-
const MonitorService: any =
|
|
949
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
950
|
-
require("../../Services/MonitorService").default;
|
|
951
|
-
|
|
952
971
|
const tenantFilter: Record<string, ObjectID> = props.tenantId
|
|
953
972
|
? { projectId: props.tenantId }
|
|
954
973
|
: {};
|
|
955
974
|
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
for (const row of serviceRows) {
|
|
968
|
-
const id: ObjectID | string | undefined = row._id;
|
|
969
|
-
if (id) {
|
|
970
|
-
result.add(id.toString());
|
|
975
|
+
const ownerTableRegistry: Map<
|
|
976
|
+
string,
|
|
977
|
+
{
|
|
978
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
979
|
+
ownerUserService: any;
|
|
980
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
981
|
+
ownerTeamService: any;
|
|
982
|
+
fkColumn: string;
|
|
983
|
+
canOwnTelemetry?: boolean;
|
|
984
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
985
|
+
modelService?: any;
|
|
971
986
|
}
|
|
972
|
-
|
|
987
|
+
> =
|
|
988
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
989
|
+
require("../Database/Permissions/OwnerTableRegistry").default;
|
|
973
990
|
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
991
|
+
/*
|
|
992
|
+
* Telemetry serviceId is polymorphic across every resource type
|
|
993
|
+
* flagged `canOwnTelemetry` in the registry (Service, Monitor, Host,
|
|
994
|
+
* DockerHost, KubernetesCluster), each of which carries labels. Find
|
|
995
|
+
* rows of each whose labels intersect the user's. Keeping this set in
|
|
996
|
+
* the registry (single source of truth) means a new telemetry-owning
|
|
997
|
+
* resource is picked up here automatically — no edits needed.
|
|
998
|
+
*/
|
|
999
|
+
for (const entry of ownerTableRegistry.values()) {
|
|
1000
|
+
if (!entry.canOwnTelemetry || !entry.modelService) {
|
|
1001
|
+
continue;
|
|
1002
|
+
}
|
|
1003
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1004
|
+
const rows: Array<any> = await entry.modelService.findBy({
|
|
1005
|
+
query: {
|
|
1006
|
+
labels: labelIds,
|
|
1007
|
+
...tenantFilter,
|
|
1008
|
+
},
|
|
1009
|
+
select: { _id: true },
|
|
1010
|
+
props: { isRoot: true },
|
|
1011
|
+
skip: 0,
|
|
1012
|
+
limit: LIMIT_MAX,
|
|
1013
|
+
});
|
|
1014
|
+
for (const row of rows) {
|
|
1015
|
+
const id: ObjectID | string | undefined = row._id;
|
|
1016
|
+
if (id) {
|
|
1017
|
+
result.add(id.toString());
|
|
1018
|
+
}
|
|
989
1019
|
}
|
|
990
1020
|
}
|
|
991
1021
|
|
|
@@ -208,15 +208,23 @@ export default class OwnedScopePermission {
|
|
|
208
208
|
): Promise<Array<ObjectID>> {
|
|
209
209
|
const model: BaseModel = new modelType();
|
|
210
210
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
211
|
+
/*
|
|
212
|
+
* Which model(s) owner tables to consult. A nested model can inherit
|
|
213
|
+
* ownership from several parent resource types when its FK is
|
|
214
|
+
* polymorphic (e.g. a telemetry serviceId that may point at a Service,
|
|
215
|
+
* Host, DockerHost or KubernetesCluster) — resolve and union the owned
|
|
216
|
+
* ids across all of them. Top-level operational resources consult
|
|
217
|
+
* their own owner tables.
|
|
218
|
+
*/
|
|
219
|
+
const resolverNames: Array<string> = model.ownedThrough
|
|
220
|
+
? model.ownedThrough.parentModels.map(
|
|
221
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
222
|
+
(parentModel: any) => {
|
|
223
|
+
return parentModel.name;
|
|
224
|
+
},
|
|
225
|
+
)
|
|
226
|
+
: // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
227
|
+
[(modelType as any).name];
|
|
220
228
|
|
|
221
229
|
/*
|
|
222
230
|
* Lazy require to avoid the circular dep cycle: this file is reachable
|
|
@@ -227,66 +235,79 @@ export default class OwnedScopePermission {
|
|
|
227
235
|
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
228
236
|
require("./OwnerTableRegistry").default;
|
|
229
237
|
|
|
230
|
-
const registryEntry: OwnerTablePair | undefined =
|
|
231
|
-
ownerTableRegistry.get(resolverName);
|
|
232
|
-
if (!registryEntry) {
|
|
233
|
-
/*
|
|
234
|
-
* No registered owner tables for this model — Owned scope can't
|
|
235
|
-
* resolve, so nothing is accessible.
|
|
236
|
-
*/
|
|
237
|
-
return [];
|
|
238
|
-
}
|
|
239
|
-
|
|
240
238
|
const seen: Set<string> = new Set<string>();
|
|
241
|
-
const fkColumn: string = registryEntry.fkColumn;
|
|
242
239
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
240
|
+
for (const resolverName of resolverNames) {
|
|
241
|
+
const registryEntry: OwnerTablePair | undefined =
|
|
242
|
+
ownerTableRegistry.get(resolverName);
|
|
243
|
+
if (!registryEntry) {
|
|
244
|
+
/*
|
|
245
|
+
* No registered owner tables for this parent — skip it. Other
|
|
246
|
+
* parents (or includeProjectScope below) may still resolve.
|
|
247
|
+
*/
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const fkColumn: string = registryEntry.fkColumn;
|
|
252
|
+
|
|
253
|
+
/*
|
|
254
|
+
* User-ownership lookup. Skipped for non-user callers (API keys,
|
|
255
|
+
* Probes with no userId); those evaluate `Owned` as `All` elsewhere.
|
|
256
|
+
*/
|
|
257
|
+
if (props.userId) {
|
|
258
|
+
const userOwnedRows: Array<BaseModel> =
|
|
259
|
+
await registryEntry.ownerUserService.findBy({
|
|
260
|
+
query: {
|
|
261
|
+
userId: props.userId,
|
|
262
|
+
...(props.tenantId ? { projectId: props.tenantId } : {}),
|
|
263
|
+
},
|
|
264
|
+
select: { [fkColumn]: true },
|
|
265
|
+
props: { isRoot: true },
|
|
266
|
+
skip: 0,
|
|
267
|
+
limit: LIMIT_MAX,
|
|
268
|
+
});
|
|
269
|
+
for (const row of userOwnedRows) {
|
|
270
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
271
|
+
const value: ObjectID | undefined = (row as any)[fkColumn];
|
|
272
|
+
if (value) {
|
|
273
|
+
seen.add(value.toString());
|
|
274
|
+
}
|
|
264
275
|
}
|
|
265
276
|
}
|
|
266
|
-
}
|
|
267
277
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
278
|
+
// Team-ownership lookup.
|
|
279
|
+
if (props.userTeamIds && props.userTeamIds.length > 0) {
|
|
280
|
+
const teamOwnedRows: Array<BaseModel> =
|
|
281
|
+
await registryEntry.ownerTeamService.findBy({
|
|
282
|
+
query: {
|
|
283
|
+
teamId: QueryHelper.any(props.userTeamIds),
|
|
284
|
+
...(props.tenantId ? { projectId: props.tenantId } : {}),
|
|
285
|
+
},
|
|
286
|
+
select: { [fkColumn]: true },
|
|
287
|
+
props: { isRoot: true },
|
|
288
|
+
skip: 0,
|
|
289
|
+
limit: LIMIT_MAX,
|
|
290
|
+
});
|
|
291
|
+
for (const row of teamOwnedRows) {
|
|
292
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
293
|
+
const value: ObjectID | undefined = (row as any)[fkColumn];
|
|
294
|
+
if (value) {
|
|
295
|
+
seen.add(value.toString());
|
|
296
|
+
}
|
|
286
297
|
}
|
|
287
298
|
}
|
|
288
299
|
}
|
|
289
300
|
|
|
301
|
+
/*
|
|
302
|
+
* Polymorphic FK rows with no owning resource (the unattributed
|
|
303
|
+
* "Unknown" telemetry bucket) carry the projectId in the FK column.
|
|
304
|
+
* They belong to the project, not any single owner, so include the
|
|
305
|
+
* tenant id when the model opts in via includeProjectScope.
|
|
306
|
+
*/
|
|
307
|
+
if (model.ownedThrough?.includeProjectScope && props.tenantId) {
|
|
308
|
+
seen.add(props.tenantId.toString());
|
|
309
|
+
}
|
|
310
|
+
|
|
290
311
|
const result: Array<ObjectID> = [];
|
|
291
312
|
for (const id of seen) {
|
|
292
313
|
result.push(new ObjectID(id));
|
|
@@ -14,10 +14,27 @@ import ScheduledMaintenanceOwnerTeamService from "../../../Services/ScheduledMai
|
|
|
14
14
|
import ScheduledMaintenanceOwnerUserService from "../../../Services/ScheduledMaintenanceOwnerUserService";
|
|
15
15
|
import ServiceOwnerTeamService from "../../../Services/ServiceOwnerTeamService";
|
|
16
16
|
import ServiceOwnerUserService from "../../../Services/ServiceOwnerUserService";
|
|
17
|
+
import HostOwnerTeamService from "../../../Services/HostOwnerTeamService";
|
|
18
|
+
import HostOwnerUserService from "../../../Services/HostOwnerUserService";
|
|
19
|
+
import DockerHostOwnerTeamService from "../../../Services/DockerHostOwnerTeamService";
|
|
20
|
+
import DockerHostOwnerUserService from "../../../Services/DockerHostOwnerUserService";
|
|
21
|
+
import KubernetesClusterOwnerTeamService from "../../../Services/KubernetesClusterOwnerTeamService";
|
|
22
|
+
import KubernetesClusterOwnerUserService from "../../../Services/KubernetesClusterOwnerUserService";
|
|
17
23
|
import StatusPageOwnerTeamService from "../../../Services/StatusPageOwnerTeamService";
|
|
18
24
|
import StatusPageOwnerUserService from "../../../Services/StatusPageOwnerUserService";
|
|
19
25
|
import WorkflowOwnerTeamService from "../../../Services/WorkflowOwnerTeamService";
|
|
20
26
|
import WorkflowOwnerUserService from "../../../Services/WorkflowOwnerUserService";
|
|
27
|
+
/*
|
|
28
|
+
* The resources whose ids can appear in a telemetry row's polymorphic
|
|
29
|
+
* serviceId also expose their own service (for label-scope resolution).
|
|
30
|
+
* Imported here so the registry is the single source of truth for the
|
|
31
|
+
* telemetry serviceId polymorphism — see canOwnTelemetry / modelService.
|
|
32
|
+
*/
|
|
33
|
+
import ServiceService from "../../../Services/ServiceService";
|
|
34
|
+
import MonitorService from "../../../Services/MonitorService";
|
|
35
|
+
import HostService from "../../../Services/HostService";
|
|
36
|
+
import DockerHostService from "../../../Services/DockerHostService";
|
|
37
|
+
import KubernetesClusterService from "../../../Services/KubernetesClusterService";
|
|
21
38
|
|
|
22
39
|
/*
|
|
23
40
|
* Maps an operational model name (e.g. "Monitor") to the two services that
|
|
@@ -35,6 +52,22 @@ export interface OwnerTablePair {
|
|
|
35
52
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
36
53
|
ownerTeamService: any;
|
|
37
54
|
fkColumn: string;
|
|
55
|
+
/*
|
|
56
|
+
* True when a telemetry row's serviceId (polymorphic, discriminated by
|
|
57
|
+
* serviceType) can reference this resource type. The analytics
|
|
58
|
+
* owned/labels scope resolution iterates the entries flagged here, so
|
|
59
|
+
* the telemetry serviceId polymorphism lives only in this registry —
|
|
60
|
+
* adding a new telemetry-owning resource needs just an entry here, with
|
|
61
|
+
* no edits in ModelPermission.
|
|
62
|
+
*/
|
|
63
|
+
canOwnTelemetry?: boolean;
|
|
64
|
+
/*
|
|
65
|
+
* The resource's own service, used by labels-scope resolution to find
|
|
66
|
+
* resources whose labels match the user's. Set for canOwnTelemetry
|
|
67
|
+
* types (they all carry labels).
|
|
68
|
+
*/
|
|
69
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
70
|
+
modelService?: any;
|
|
38
71
|
}
|
|
39
72
|
|
|
40
73
|
const ownerTableRegistry: Map<string, OwnerTablePair> = new Map<
|
|
@@ -47,6 +80,8 @@ const ownerTableRegistry: Map<string, OwnerTablePair> = new Map<
|
|
|
47
80
|
ownerUserService: MonitorOwnerUserService,
|
|
48
81
|
ownerTeamService: MonitorOwnerTeamService,
|
|
49
82
|
fkColumn: "monitorId",
|
|
83
|
+
canOwnTelemetry: true,
|
|
84
|
+
modelService: MonitorService,
|
|
50
85
|
},
|
|
51
86
|
],
|
|
52
87
|
[
|
|
@@ -111,6 +146,38 @@ const ownerTableRegistry: Map<string, OwnerTablePair> = new Map<
|
|
|
111
146
|
ownerUserService: ServiceOwnerUserService,
|
|
112
147
|
ownerTeamService: ServiceOwnerTeamService,
|
|
113
148
|
fkColumn: "serviceId",
|
|
149
|
+
canOwnTelemetry: true,
|
|
150
|
+
modelService: ServiceService,
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
[
|
|
154
|
+
"Host",
|
|
155
|
+
{
|
|
156
|
+
ownerUserService: HostOwnerUserService,
|
|
157
|
+
ownerTeamService: HostOwnerTeamService,
|
|
158
|
+
fkColumn: "hostId",
|
|
159
|
+
canOwnTelemetry: true,
|
|
160
|
+
modelService: HostService,
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
[
|
|
164
|
+
"DockerHost",
|
|
165
|
+
{
|
|
166
|
+
ownerUserService: DockerHostOwnerUserService,
|
|
167
|
+
ownerTeamService: DockerHostOwnerTeamService,
|
|
168
|
+
fkColumn: "dockerHostId",
|
|
169
|
+
canOwnTelemetry: true,
|
|
170
|
+
modelService: DockerHostService,
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
[
|
|
174
|
+
"KubernetesCluster",
|
|
175
|
+
{
|
|
176
|
+
ownerUserService: KubernetesClusterOwnerUserService,
|
|
177
|
+
ownerTeamService: KubernetesClusterOwnerTeamService,
|
|
178
|
+
fkColumn: "kubernetesClusterId",
|
|
179
|
+
canOwnTelemetry: true,
|
|
180
|
+
modelService: KubernetesClusterService,
|
|
114
181
|
},
|
|
115
182
|
],
|
|
116
183
|
[
|
package/Server/Utils/Logger.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { LogLevel } from "../EnvironmentConfig";
|
|
2
2
|
import OneUptimeTelemetry, { TelemetryLogger } from "./Telemetry";
|
|
3
|
+
import TelemetryContext from "./Telemetry/TelemetryContext";
|
|
3
4
|
import { SeverityNumber } from "@opentelemetry/api-logs";
|
|
4
5
|
import Exception from "../../Types/Exception/Exception";
|
|
5
6
|
import { JSONObject } from "../../Types/JSON";
|
|
@@ -171,9 +172,19 @@ export default class logger {
|
|
|
171
172
|
return;
|
|
172
173
|
}
|
|
173
174
|
|
|
175
|
+
/*
|
|
176
|
+
* Merge ambient TelemetryContext attributes (projectId, userId,
|
|
177
|
+
* monitorId, requestId, ...) into every log record. Attributes passed
|
|
178
|
+
* explicitly to the log call take precedence over the ambient context.
|
|
179
|
+
*/
|
|
180
|
+
const mergedAttributes: LogAttributes = {
|
|
181
|
+
...TelemetryContext.getAttributes(),
|
|
182
|
+
...(data.attributes || {}),
|
|
183
|
+
};
|
|
184
|
+
|
|
174
185
|
const sanitizedAttributes:
|
|
175
186
|
| Record<string, string | number | boolean>
|
|
176
|
-
| undefined = this.sanitizeAttributes(
|
|
187
|
+
| undefined = this.sanitizeAttributes(mergedAttributes);
|
|
177
188
|
|
|
178
189
|
logger.emit({
|
|
179
190
|
body: this.serializeLogBody(data.body),
|
|
@@ -29,6 +29,7 @@ import logger, {
|
|
|
29
29
|
import "./Process";
|
|
30
30
|
import Response from "./Response";
|
|
31
31
|
import SpanUtil from "./Telemetry/SpanUtil";
|
|
32
|
+
import TelemetryContext from "./Telemetry/TelemetryContext";
|
|
32
33
|
import { api } from "@opentelemetry/sdk-node";
|
|
33
34
|
import StatusCode from "../../Types/API/StatusCode";
|
|
34
35
|
import HTTPErrorResponse from "../../Types/API/HTTPErrorResponse";
|
|
@@ -213,12 +214,19 @@ app.use((req: ExpressRequest, _res: ExpressResponse, next: NextFunction) => {
|
|
|
213
214
|
const requestId: string = crypto.randomUUID();
|
|
214
215
|
(req as OneUptimeRequest).requestId = requestId;
|
|
215
216
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
217
|
+
/*
|
|
218
|
+
* Open a telemetry-context scope for the entire request. requestId is seeded
|
|
219
|
+
* here; projectId/userId are added later by the auth middleware. Because
|
|
220
|
+
* ContextSpanProcessor and Logger read this ambient context, every span and
|
|
221
|
+
* log produced downstream inherits it automatically.
|
|
222
|
+
*/
|
|
223
|
+
TelemetryContext.runWithContext({ requestId: requestId }, () => {
|
|
224
|
+
SpanUtil.addAttributesToCurrentSpan({
|
|
225
|
+
requestId: requestId,
|
|
226
|
+
});
|
|
220
227
|
|
|
221
|
-
|
|
228
|
+
next();
|
|
229
|
+
});
|
|
222
230
|
});
|
|
223
231
|
|
|
224
232
|
export interface InitFuctionOptions {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import TelemetryContext from "./TelemetryContext";
|
|
2
|
+
import type { Context } from "@opentelemetry/api";
|
|
3
|
+
import type {
|
|
4
|
+
ReadableSpan,
|
|
5
|
+
Span,
|
|
6
|
+
SpanProcessor,
|
|
7
|
+
} from "@opentelemetry/sdk-trace-base";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Copies the ambient {@link TelemetryContext} attributes (projectId, userId,
|
|
11
|
+
* monitorId, incidentId, requestId, ...) onto every span at creation time.
|
|
12
|
+
*
|
|
13
|
+
* Combined with `TelemetryContext` scopes seeded at each entry point (HTTP
|
|
14
|
+
* request, worker job, probe check, cron run), this makes the full
|
|
15
|
+
* tenant/business context queryable on all spans — including the ~1958
|
|
16
|
+
* attribute-less `@CaptureSpan` spans — without touching any of those call
|
|
17
|
+
* sites.
|
|
18
|
+
*/
|
|
19
|
+
export default class ContextSpanProcessor implements SpanProcessor {
|
|
20
|
+
public onStart(span: Span, _parentContext: Context): void {
|
|
21
|
+
try {
|
|
22
|
+
const attributes: Record<string, string | number | boolean> =
|
|
23
|
+
TelemetryContext.getAttributes();
|
|
24
|
+
|
|
25
|
+
for (const key in attributes) {
|
|
26
|
+
const value: string | number | boolean | undefined = attributes[key];
|
|
27
|
+
|
|
28
|
+
if (value !== undefined && value !== null) {
|
|
29
|
+
span.setAttribute(key, value);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
} catch {
|
|
33
|
+
// Context enrichment must never break span creation.
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public onEnd(_span: ReadableSpan): void {
|
|
38
|
+
// no-op: enrichment happens entirely in onStart.
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public shutdown(): Promise<void> {
|
|
42
|
+
return Promise.resolve();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public forceFlush(): Promise<void> {
|
|
46
|
+
return Promise.resolve();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import OpenTelemetryAPI, { Span } from "@opentelemetry/api";
|
|
2
1
|
import { DisableTelemetry } from "../../EnvironmentConfig";
|
|
2
|
+
import TelemetryContext from "./TelemetryContext";
|
|
3
|
+
import OpenTelemetryAPI, { Span } from "@opentelemetry/api";
|
|
3
4
|
|
|
4
5
|
export interface SpanAttributes {
|
|
5
6
|
userId?: string | undefined;
|
|
@@ -21,14 +22,26 @@ export interface SpanAttributes {
|
|
|
21
22
|
|
|
22
23
|
export default class SpanUtil {
|
|
23
24
|
/**
|
|
24
|
-
* Add attributes to the
|
|
25
|
-
*
|
|
25
|
+
* Add attributes to the current unit of work.
|
|
26
|
+
*
|
|
27
|
+
* This does two things:
|
|
28
|
+
* 1. Merges the attributes into the ambient {@link TelemetryContext} so that
|
|
29
|
+
* every span and log produced later in this request/job/check inherits
|
|
30
|
+
* them (OTel span attributes do NOT propagate parent -> child on their
|
|
31
|
+
* own, so this is what actually makes context flow downstream).
|
|
32
|
+
* 2. Tags the currently active span immediately, if there is one.
|
|
33
|
+
*
|
|
34
|
+
* Safe to call even when there is no active span or scope, or when telemetry
|
|
35
|
+
* is disabled.
|
|
26
36
|
*/
|
|
27
37
|
public static addAttributesToCurrentSpan(attributes: SpanAttributes): void {
|
|
28
38
|
if (DisableTelemetry) {
|
|
29
39
|
return;
|
|
30
40
|
}
|
|
31
41
|
|
|
42
|
+
// Propagate to all downstream spans + logs via the ambient context.
|
|
43
|
+
TelemetryContext.setAttributes(attributes);
|
|
44
|
+
|
|
32
45
|
const span: Span | undefined = OpenTelemetryAPI.trace.getActiveSpan();
|
|
33
46
|
|
|
34
47
|
if (!span) {
|
|
@@ -55,36 +68,4 @@ export default class SpanUtil {
|
|
|
55
68
|
}
|
|
56
69
|
}
|
|
57
70
|
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Build span attributes from a request-like object.
|
|
61
|
-
* Similar to getLogAttributesFromRequest in Logger but for spans.
|
|
62
|
-
*/
|
|
63
|
-
public static getSpanAttributesFromRequest(
|
|
64
|
-
req?: {
|
|
65
|
-
requestId?: string;
|
|
66
|
-
tenantId?: { toString(): string };
|
|
67
|
-
userAuthorization?: { userId?: { toString(): string } };
|
|
68
|
-
} | null,
|
|
69
|
-
): SpanAttributes {
|
|
70
|
-
if (!req) {
|
|
71
|
-
return {};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const attributes: SpanAttributes = {};
|
|
75
|
-
|
|
76
|
-
if (req.requestId) {
|
|
77
|
-
attributes["requestId"] = req.requestId;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (req.tenantId) {
|
|
81
|
-
attributes["projectId"] = req.tenantId.toString();
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (req.userAuthorization?.userId) {
|
|
85
|
-
attributes["userId"] = req.userAuthorization.userId.toString();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return attributes;
|
|
89
|
-
}
|
|
90
71
|
}
|