@oneuptime/common 10.4.9 → 10.4.11

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.
Files changed (107) hide show
  1. package/Models/AnalyticsModels/ExceptionInstance.ts +47 -1
  2. package/Models/AnalyticsModels/Log.ts +47 -1
  3. package/Models/AnalyticsModels/Metric.ts +1 -7
  4. package/Models/AnalyticsModels/Profile.ts +47 -1
  5. package/Models/AnalyticsModels/ProfileSample.ts +47 -1
  6. package/Models/AnalyticsModels/Span.ts +47 -1
  7. package/Models/DatabaseModels/DockerHost.ts +83 -0
  8. package/Models/DatabaseModels/Host.ts +83 -0
  9. package/Models/DatabaseModels/Index.ts +0 -2
  10. package/Models/DatabaseModels/KubernetesCluster.ts +83 -0
  11. package/Server/Infrastructure/Postgres/SchemaMigrations/1779199346010-AddTelemetryRetentionConfig.ts +1 -1
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1779277271302-DropServiceDependencyTable.ts +44 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1779282769946-AddTelemetryRetentionToHostDockerKubernetes.ts +50 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  15. package/Server/Services/AlertService.ts +2 -4
  16. package/Server/Services/IncidentService.ts +2 -4
  17. package/Server/Services/Index.ts +0 -2
  18. package/Server/Services/LogAggregationService.ts +54 -6
  19. package/Server/Services/OpenTelemetryIngestService.ts +132 -0
  20. package/Server/Services/TraceAggregationService.ts +83 -8
  21. package/Server/Utils/Monitor/MonitorMetricUtil.ts +2 -4
  22. package/Server/Utils/Telemetry/Telemetry.ts +38 -0
  23. package/Tests/Server/Middleware/UserAuthorization.test.ts +5 -0
  24. package/Types/Permission.ts +0 -46
  25. package/Types/Telemetry/ServiceType.ts +19 -0
  26. package/Types/Time/RangeStartAndEndDateTime.ts +8 -0
  27. package/Types/Time/TimeRange.ts +1 -0
  28. package/UI/Components/Forms/Validation.ts +2 -2
  29. package/UI/Components/LogsViewer/LogsViewer.tsx +135 -17
  30. package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +1 -0
  31. package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +84 -1
  32. package/UI/Components/Telemetry/TelemetryRetentionConfigForm.tsx +0 -1
  33. package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +1 -0
  34. package/build/dist/Models/AnalyticsModels/ExceptionInstance.js +41 -1
  35. package/build/dist/Models/AnalyticsModels/ExceptionInstance.js.map +1 -1
  36. package/build/dist/Models/AnalyticsModels/Log.js +41 -1
  37. package/build/dist/Models/AnalyticsModels/Log.js.map +1 -1
  38. package/build/dist/Models/AnalyticsModels/Metric.js +0 -7
  39. package/build/dist/Models/AnalyticsModels/Metric.js.map +1 -1
  40. package/build/dist/Models/AnalyticsModels/Profile.js +41 -1
  41. package/build/dist/Models/AnalyticsModels/Profile.js.map +1 -1
  42. package/build/dist/Models/AnalyticsModels/ProfileSample.js +41 -1
  43. package/build/dist/Models/AnalyticsModels/ProfileSample.js.map +1 -1
  44. package/build/dist/Models/AnalyticsModels/Span.js +41 -1
  45. package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
  46. package/build/dist/Models/DatabaseModels/DockerHost.js +84 -0
  47. package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
  48. package/build/dist/Models/DatabaseModels/Host.js +84 -0
  49. package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
  50. package/build/dist/Models/DatabaseModels/Index.js +0 -2
  51. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  52. package/build/dist/Models/DatabaseModels/KubernetesCluster.js +84 -0
  53. package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
  54. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779199346010-AddTelemetryRetentionConfig.js.map +1 -1
  55. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779277271302-DropServiceDependencyTable.js +42 -0
  56. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779277271302-DropServiceDependencyTable.js.map +1 -0
  57. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779282769946-AddTelemetryRetentionToHostDockerKubernetes.js +22 -0
  58. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779282769946-AddTelemetryRetentionToHostDockerKubernetes.js.map +1 -0
  59. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  60. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  61. package/build/dist/Server/Services/AlertService.js +2 -1
  62. package/build/dist/Server/Services/AlertService.js.map +1 -1
  63. package/build/dist/Server/Services/IncidentService.js +2 -1
  64. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  65. package/build/dist/Server/Services/Index.js +0 -2
  66. package/build/dist/Server/Services/Index.js.map +1 -1
  67. package/build/dist/Server/Services/LogAggregationService.js +46 -4
  68. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  69. package/build/dist/Server/Services/OpenTelemetryIngestService.js +103 -0
  70. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  71. package/build/dist/Server/Services/TraceAggregationService.js +66 -6
  72. package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
  73. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +2 -1
  74. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
  75. package/build/dist/Server/Utils/Telemetry/Telemetry.js +26 -0
  76. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  77. package/build/dist/Tests/Server/Middleware/UserAuthorization.test.js +3 -0
  78. package/build/dist/Tests/Server/Middleware/UserAuthorization.test.js.map +1 -1
  79. package/build/dist/Types/Permission.js +0 -40
  80. package/build/dist/Types/Permission.js.map +1 -1
  81. package/build/dist/Types/Telemetry/ServiceType.js +20 -0
  82. package/build/dist/Types/Telemetry/ServiceType.js.map +1 -0
  83. package/build/dist/Types/Time/RangeStartAndEndDateTime.js +4 -0
  84. package/build/dist/Types/Time/RangeStartAndEndDateTime.js.map +1 -1
  85. package/build/dist/Types/Time/TimeRange.js +1 -0
  86. package/build/dist/Types/Time/TimeRange.js.map +1 -1
  87. package/build/dist/UI/Components/Forms/Validation.js +2 -2
  88. package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
  89. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +106 -16
  90. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  91. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +1 -0
  92. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
  93. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +67 -1
  94. package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
  95. package/build/dist/UI/Components/Telemetry/TelemetryRetentionConfigForm.js.map +1 -1
  96. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +1 -0
  97. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
  98. package/package.json +1 -1
  99. package/Models/DatabaseModels/ServiceDependency.ts +0 -529
  100. package/Server/Services/ServiceDependencyService.ts +0 -48
  101. package/UI/Components/Graphs/ServiceDependencyGraph.tsx +0 -286
  102. package/build/dist/Models/DatabaseModels/ServiceDependency.js +0 -545
  103. package/build/dist/Models/DatabaseModels/ServiceDependency.js.map +0 -1
  104. package/build/dist/Server/Services/ServiceDependencyService.js +0 -47
  105. package/build/dist/Server/Services/ServiceDependencyService.js.map +0 -1
  106. package/build/dist/UI/Components/Graphs/ServiceDependencyGraph.js +0 -206
  107. package/build/dist/UI/Components/Graphs/ServiceDependencyGraph.js.map +0 -1
@@ -19,6 +19,7 @@ import UniqueColumnBy from "../../Types/Database/UniqueColumnBy";
19
19
  import IconProp from "../../Types/Icon/IconProp";
20
20
  import ObjectID from "../../Types/ObjectID";
21
21
  import Permission from "../../Types/Permission";
22
+ import TelemetryRetentionConfig from "../../Types/Telemetry/TelemetryRetentionConfig";
22
23
  import {
23
24
  Column,
24
25
  Entity,
@@ -725,4 +726,86 @@ export default class KubernetesCluster extends BaseModel {
725
726
  },
726
727
  })
727
728
  public labels?: Array<Label> = undefined;
729
+
730
+ @ColumnAccessControl({
731
+ create: [
732
+ Permission.ProjectOwner,
733
+ Permission.ProjectAdmin,
734
+ Permission.ProjectMember,
735
+ Permission.SettingsAdmin,
736
+ Permission.SettingsMember,
737
+ Permission.CreateKubernetesCluster,
738
+ ],
739
+ read: [
740
+ Permission.ProjectOwner,
741
+ Permission.ProjectAdmin,
742
+ Permission.ProjectMember,
743
+ Permission.Viewer,
744
+ Permission.SettingsAdmin,
745
+ Permission.SettingsMember,
746
+ Permission.SettingsViewer,
747
+ Permission.ReadKubernetesCluster,
748
+ ],
749
+ update: [
750
+ Permission.ProjectOwner,
751
+ Permission.ProjectAdmin,
752
+ Permission.ProjectMember,
753
+ Permission.SettingsAdmin,
754
+ Permission.SettingsMember,
755
+ Permission.EditKubernetesCluster,
756
+ ],
757
+ })
758
+ @TableColumn({
759
+ type: TableColumnType.Number,
760
+ title: "Retain Telemetry Data For Days",
761
+ description:
762
+ "Number of days to retain telemetry data for this Kubernetes cluster. Leave blank to use the project-wide default.",
763
+ })
764
+ @Column({
765
+ type: ColumnType.Number,
766
+ nullable: true,
767
+ unique: false,
768
+ })
769
+ public retainTelemetryDataForDays?: number = undefined;
770
+
771
+ @ColumnAccessControl({
772
+ create: [
773
+ Permission.ProjectOwner,
774
+ Permission.ProjectAdmin,
775
+ Permission.ProjectMember,
776
+ Permission.SettingsAdmin,
777
+ Permission.SettingsMember,
778
+ Permission.CreateKubernetesCluster,
779
+ ],
780
+ read: [
781
+ Permission.ProjectOwner,
782
+ Permission.ProjectAdmin,
783
+ Permission.ProjectMember,
784
+ Permission.Viewer,
785
+ Permission.SettingsAdmin,
786
+ Permission.SettingsMember,
787
+ Permission.SettingsViewer,
788
+ Permission.ReadKubernetesCluster,
789
+ ],
790
+ update: [
791
+ Permission.ProjectOwner,
792
+ Permission.ProjectAdmin,
793
+ Permission.ProjectMember,
794
+ Permission.SettingsAdmin,
795
+ Permission.SettingsMember,
796
+ Permission.EditKubernetesCluster,
797
+ ],
798
+ })
799
+ @TableColumn({
800
+ type: TableColumnType.JSON,
801
+ required: false,
802
+ title: "Telemetry Data Retention Overrides",
803
+ description:
804
+ "Per-pillar retention overrides for this Kubernetes cluster (logs by severity, traces by status, metrics, profiles). Unset fields fall back to the cluster default, then the project's retention settings.",
805
+ })
806
+ @Column({
807
+ type: ColumnType.JSON,
808
+ nullable: true,
809
+ })
810
+ public telemetryRetentionConfig?: TelemetryRetentionConfig = undefined;
728
811
  }
@@ -3,7 +3,7 @@ import { MigrationInterface, QueryRunner } from "typeorm";
3
3
  export class AddTelemetryRetentionConfig1779199346010
4
4
  implements MigrationInterface
5
5
  {
6
- name = "AddTelemetryRetentionConfig1779199346010";
6
+ public name: string = "AddTelemetryRetentionConfig1779199346010";
7
7
 
8
8
  public async up(queryRunner: QueryRunner): Promise<void> {
9
9
  await queryRunner.query(
@@ -0,0 +1,44 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+ import CaptureSpan from "../../../Utils/Telemetry/CaptureSpan";
3
+
4
+ export class DropServiceDependencyTable1779277271302
5
+ implements MigrationInterface
6
+ {
7
+ public name = "DropServiceDependencyTable1779277271302";
8
+
9
+ @CaptureSpan()
10
+ public async up(queryRunner: QueryRunner): Promise<void> {
11
+ await queryRunner.query(`DROP TABLE IF EXISTS "ServiceDependency" CASCADE`);
12
+ }
13
+
14
+ @CaptureSpan()
15
+ public async down(queryRunner: QueryRunner): Promise<void> {
16
+ await queryRunner.query(
17
+ `CREATE TABLE "ServiceDependency" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP WITH TIME ZONE, "version" integer NOT NULL, "projectId" uuid NOT NULL, "serviceId" uuid NOT NULL, "dependencyServiceId" uuid NOT NULL, "createdByUserId" uuid, "deletedByUserId" uuid, CONSTRAINT "PK_f4c8003faa6daec34a5b97f88e8" PRIMARY KEY ("_id"))`,
18
+ );
19
+ await queryRunner.query(
20
+ `CREATE INDEX "IDX_3e2a1ef7e8795a46c2e2e9955c" ON "ServiceDependency" ("projectId")`,
21
+ );
22
+ await queryRunner.query(
23
+ `CREATE INDEX "IDX_3af01e69f3e28ae56a968bbd81" ON "ServiceDependency" ("serviceId")`,
24
+ );
25
+ await queryRunner.query(
26
+ `CREATE INDEX "IDX_c2562045902370ec46091924fc" ON "ServiceDependency" ("dependencyServiceId")`,
27
+ );
28
+ await queryRunner.query(
29
+ `ALTER TABLE "ServiceDependency" ADD CONSTRAINT "FK_3e2a1ef7e8795a46c2e2e9955c2" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
30
+ );
31
+ await queryRunner.query(
32
+ `ALTER TABLE "ServiceDependency" ADD CONSTRAINT "FK_3af01e69f3e28ae56a968bbd812" FOREIGN KEY ("serviceId") REFERENCES "Service"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
33
+ );
34
+ await queryRunner.query(
35
+ `ALTER TABLE "ServiceDependency" ADD CONSTRAINT "FK_c2562045902370ec46091924fc8" FOREIGN KEY ("dependencyServiceId") REFERENCES "Service"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
36
+ );
37
+ await queryRunner.query(
38
+ `ALTER TABLE "ServiceDependency" ADD CONSTRAINT "FK_cc8c8d41106bebab21d1c69e129" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
39
+ );
40
+ await queryRunner.query(
41
+ `ALTER TABLE "ServiceDependency" ADD CONSTRAINT "FK_feb830e4e4f8259a871105b16e8" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
42
+ );
43
+ }
44
+ }
@@ -0,0 +1,50 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class AddTelemetryRetentionToHostDockerKubernetes1779282769946
4
+ implements MigrationInterface
5
+ {
6
+ public name: string =
7
+ "AddTelemetryRetentionToHostDockerKubernetes1779282769946";
8
+
9
+ public async up(queryRunner: QueryRunner): Promise<void> {
10
+ await queryRunner.query(
11
+ `ALTER TABLE "Host" ADD "retainTelemetryDataForDays" integer`,
12
+ );
13
+ await queryRunner.query(
14
+ `ALTER TABLE "Host" ADD "telemetryRetentionConfig" jsonb`,
15
+ );
16
+ await queryRunner.query(
17
+ `ALTER TABLE "DockerHost" ADD "retainTelemetryDataForDays" integer`,
18
+ );
19
+ await queryRunner.query(
20
+ `ALTER TABLE "DockerHost" ADD "telemetryRetentionConfig" jsonb`,
21
+ );
22
+ await queryRunner.query(
23
+ `ALTER TABLE "KubernetesCluster" ADD "retainTelemetryDataForDays" integer`,
24
+ );
25
+ await queryRunner.query(
26
+ `ALTER TABLE "KubernetesCluster" ADD "telemetryRetentionConfig" jsonb`,
27
+ );
28
+ }
29
+
30
+ public async down(queryRunner: QueryRunner): Promise<void> {
31
+ await queryRunner.query(
32
+ `ALTER TABLE "KubernetesCluster" DROP COLUMN "telemetryRetentionConfig"`,
33
+ );
34
+ await queryRunner.query(
35
+ `ALTER TABLE "KubernetesCluster" DROP COLUMN "retainTelemetryDataForDays"`,
36
+ );
37
+ await queryRunner.query(
38
+ `ALTER TABLE "DockerHost" DROP COLUMN "telemetryRetentionConfig"`,
39
+ );
40
+ await queryRunner.query(
41
+ `ALTER TABLE "DockerHost" DROP COLUMN "retainTelemetryDataForDays"`,
42
+ );
43
+ await queryRunner.query(
44
+ `ALTER TABLE "Host" DROP COLUMN "telemetryRetentionConfig"`,
45
+ );
46
+ await queryRunner.query(
47
+ `ALTER TABLE "Host" DROP COLUMN "retainTelemetryDataForDays"`,
48
+ );
49
+ }
50
+ }
@@ -338,6 +338,8 @@ import { AddOnCallDutyPolicyScheduleOwners1778929624633 } from "./1778929624633-
338
338
  import { AddOnCallIncomingCallOwnersAndRules1778931537020 } from "./1778931537020-AddOnCallIncomingCallOwnersAndRules";
339
339
  import { IncreaseSmtpUsernameLength1779125489830 } from "./1779125489830-IncreaseSmtpUsernameLength";
340
340
  import { AddTelemetryRetentionConfig1779199346010 } from "./1779199346010-AddTelemetryRetentionConfig";
341
+ import { DropServiceDependencyTable1779277271302 } from "./1779277271302-DropServiceDependencyTable";
342
+ import { AddTelemetryRetentionToHostDockerKubernetes1779282769946 } from "./1779282769946-AddTelemetryRetentionToHostDockerKubernetes";
341
343
  export default [
342
344
  InitialMigration,
343
345
  MigrationName1717678334852,
@@ -679,4 +681,6 @@ export default [
679
681
  AddOnCallIncomingCallOwnersAndRules1778931537020,
680
682
  IncreaseSmtpUsernameLength1779125489830,
681
683
  AddTelemetryRetentionConfig1779199346010,
684
+ DropServiceDependencyTable1779277271302,
685
+ AddTelemetryRetentionToHostDockerKubernetes1779282769946,
682
686
  ];
@@ -38,10 +38,8 @@ import MetricService from "./MetricService";
38
38
  import GlobalConfigService from "./GlobalConfigService";
39
39
  import GlobalConfig from "../../Models/DatabaseModels/GlobalConfig";
40
40
  import OneUptimeDate from "../../Types/Date";
41
- import Metric, {
42
- MetricPointType,
43
- ServiceType,
44
- } from "../../Models/AnalyticsModels/Metric";
41
+ import Metric, { MetricPointType } from "../../Models/AnalyticsModels/Metric";
42
+ import ServiceType from "../../Types/Telemetry/ServiceType";
45
43
  import AlertMetricType from "../../Types/Alerts/AlertMetricType";
46
44
  import AlertFeedService from "./AlertFeedService";
47
45
  import { AlertFeedEventType } from "../../Models/DatabaseModels/AlertFeed";
@@ -42,10 +42,8 @@ import MetricService from "./MetricService";
42
42
  import GlobalConfigService from "./GlobalConfigService";
43
43
  import GlobalConfig from "../../Models/DatabaseModels/GlobalConfig";
44
44
  import IncidentMetricType from "../../Types/Incident/IncidentMetricType";
45
- import Metric, {
46
- MetricPointType,
47
- ServiceType,
48
- } from "../../Models/AnalyticsModels/Metric";
45
+ import Metric, { MetricPointType } from "../../Models/AnalyticsModels/Metric";
46
+ import ServiceType from "../../Types/Telemetry/ServiceType";
49
47
  import OneUptimeDate from "../../Types/Date";
50
48
  import TelemetryUtil from "../Utils/Telemetry/Telemetry";
51
49
  import logger, { LogAttributes } from "../Utils/Logger";
@@ -161,7 +161,6 @@ import WorkflowLogService from "./WorkflowLogService";
161
161
  import WorkflowService from "./WorkflowService";
162
162
  import WorkflowVariablesService from "./WorkflowVariableService";
163
163
  import AnalyticsBaseModel from "../../Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel";
164
- import ServiceDependencyService from "./ServiceDependencyService";
165
164
  import TelemetryExceptionService from "./TelemetryExceptionService";
166
165
  import ExceptionInstanceService from "./ExceptionInstanceService";
167
166
  import ScheduledMaintenanceTemplateService from "./ScheduledMaintenanceTemplateService";
@@ -388,7 +387,6 @@ const services: Array<BaseService> = [
388
387
  ServiceService,
389
388
  ServiceOwnerTeamService,
390
389
  ServiceOwnerUserService,
391
- ServiceDependencyService,
392
390
  ServiceMonitorService,
393
391
  ServiceCodeRepositoryService,
394
392
 
@@ -8,6 +8,7 @@ import Includes from "../../Types/BaseDatabase/Includes";
8
8
  import AnalyticsTableName from "../../Types/AnalyticsDatabase/AnalyticsTableName";
9
9
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
10
10
  import { DbJSONResponse, Results } from "./AnalyticsDatabaseService";
11
+ import ServiceType from "../../Types/Telemetry/ServiceType";
11
12
 
12
13
  export interface HistogramBucket {
13
14
  time: string;
@@ -92,6 +93,19 @@ export class LogAggregationService {
92
93
  "traceId",
93
94
  "spanId",
94
95
  ]);
96
+ /*
97
+ * Virtual facet keys that don't correspond to real ClickHouse columns —
98
+ * they all read out of `serviceId` filtered by `serviceType`. The
99
+ * discriminator was added so host / docker host / k8s cluster telemetry
100
+ * could reuse the `serviceId` slot instead of synthesising phantom
101
+ * Service rows; these facets surface each resource type independently.
102
+ */
103
+ private static readonly RESOURCE_FACET_KEYS: Map<string, ServiceType> =
104
+ new Map([
105
+ ["hostId", ServiceType.Host],
106
+ ["dockerHostId", ServiceType.DockerHost],
107
+ ["kubernetesClusterId", ServiceType.KubernetesCluster],
108
+ ]);
95
109
  private static readonly ATTRIBUTE_KEY_PATTERN: RegExp = /^[a-zA-Z0-9._:/-]+$/;
96
110
  private static readonly MAX_FACET_KEY_LENGTH: number = 256;
97
111
 
@@ -192,13 +206,25 @@ export class LogAggregationService {
192
206
 
193
207
  LogAggregationService.validateFacetKey(request.facetKey);
194
208
 
195
- const isTopLevelColumn: boolean = LogAggregationService.isTopLevelColumn(
196
- request.facetKey,
197
- );
209
+ const resourceServiceType: ServiceType | undefined =
210
+ LogAggregationService.RESOURCE_FACET_KEYS.get(request.facetKey);
211
+ const isResourceFacet: boolean = resourceServiceType !== undefined;
212
+ const isTopLevelColumn: boolean =
213
+ isResourceFacet ||
214
+ LogAggregationService.isTopLevelColumn(request.facetKey);
198
215
 
199
216
  const statement: Statement = new Statement();
200
217
 
201
- if (isTopLevelColumn) {
218
+ if (isResourceFacet) {
219
+ /*
220
+ * Virtual facet — group serviceId values whose row carries the
221
+ * matching ServiceType discriminator (Host / DockerHost /
222
+ * KubernetesCluster).
223
+ */
224
+ statement.append(
225
+ SQL`SELECT toString(serviceId) AS val, count() AS cnt FROM ${LogAggregationService.TABLE_NAME}`,
226
+ );
227
+ } else if (isTopLevelColumn) {
202
228
  statement.append(
203
229
  SQL`SELECT toString(${request.facetKey}) AS val, count() AS cnt FROM ${LogAggregationService.TABLE_NAME}`,
204
230
  );
@@ -224,7 +250,26 @@ export class LogAggregationService {
224
250
  }}`,
225
251
  );
226
252
 
227
- if (!isTopLevelColumn) {
253
+ if (isResourceFacet) {
254
+ statement.append(
255
+ SQL` AND serviceType = ${{
256
+ type: TableColumnType.Text,
257
+ value: resourceServiceType as string,
258
+ }}`,
259
+ );
260
+ } else if (request.facetKey === "serviceId") {
261
+ /*
262
+ * Constrain the canonical Services facet to rows that actually
263
+ * belong to a Service. NULL / empty serviceType covers legacy
264
+ * rows ingested before the discriminator existed.
265
+ */
266
+ statement.append(
267
+ SQL` AND (serviceType = '' OR serviceType = ${{
268
+ type: TableColumnType.Text,
269
+ value: ServiceType.OpenTelemetry as string,
270
+ }})`,
271
+ );
272
+ } else if (!isTopLevelColumn) {
228
273
  statement.append(
229
274
  SQL` AND JSONHas(attributes, ${{
230
275
  type: TableColumnType.Text,
@@ -944,7 +989,10 @@ export class LogAggregationService {
944
989
  throw new BadDataException("Invalid facetKey");
945
990
  }
946
991
 
947
- if (LogAggregationService.isTopLevelColumn(facetKey)) {
992
+ if (
993
+ LogAggregationService.isTopLevelColumn(facetKey) ||
994
+ LogAggregationService.RESOURCE_FACET_KEYS.has(facetKey)
995
+ ) {
948
996
  return;
949
997
  }
950
998
 
@@ -16,6 +16,13 @@ import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
16
16
  import SortOrder from "../../Types/BaseDatabase/SortOrder";
17
17
  import logger from "../Utils/Logger";
18
18
  import TelemetryRetentionConfig from "../../Types/Telemetry/TelemetryRetentionConfig";
19
+ import ServiceType from "../../Types/Telemetry/ServiceType";
20
+ import Host from "../../Models/DatabaseModels/Host";
21
+ import DockerHost from "../../Models/DatabaseModels/DockerHost";
22
+ import KubernetesCluster from "../../Models/DatabaseModels/KubernetesCluster";
23
+ import HostService from "./HostService";
24
+ import DockerHostService from "./DockerHostService";
25
+ import KubernetesClusterService from "./KubernetesClusterService";
19
26
 
20
27
  export enum OtelAggregationTemporality {
21
28
  Cumulative = "AGGREGATION_TEMPORALITY_CUMULATIVE",
@@ -25,6 +32,14 @@ export enum OtelAggregationTemporality {
25
32
  export interface TelemetryServiceMetadata {
26
33
  serviceName: string;
27
34
  serviceId: ObjectID;
35
+ /*
36
+ * Discriminator stamped on every analytics row so the read side
37
+ * knows which Postgres table the `serviceId` actually points at
38
+ * (real Service, Host, DockerHost, KubernetesCluster, Monitor, …).
39
+ * Defaults to OpenTelemetry for legacy ingest paths that go through
40
+ * `telemetryServiceFromName`.
41
+ */
42
+ serviceType: ServiceType;
28
43
  dataRententionInDays: number;
29
44
  serviceRetentionConfig: TelemetryRetentionConfig | null;
30
45
  serviceRetentionInDays: number | null;
@@ -166,6 +181,7 @@ export default class OTelIngestService {
166
181
  return {
167
182
  serviceName: data.serviceName,
168
183
  serviceId: svc.id!,
184
+ serviceType: ServiceType.OpenTelemetry,
169
185
  dataRententionInDays:
170
186
  serviceLevelRetention || projectContext.projectRetentionInDays,
171
187
  serviceRetentionConfig: svc.telemetryRetentionConfig ?? null,
@@ -225,6 +241,122 @@ export default class OTelIngestService {
225
241
 
226
242
  return buildMetadata(service);
227
243
  }
244
+
245
+ /*
246
+ * Builds a TelemetryServiceMetadata for a non-Service resource —
247
+ * Host, DockerHost, KubernetesCluster, Monitor. These resources
248
+ * own telemetry directly via their own Postgres id (stamped into
249
+ * the analytics row's `serviceId` column) and do not have a paired
250
+ * Service row. Per-resource retention overrides (when set on the
251
+ * Host / DockerHost / KubernetesCluster row) are honoured here so
252
+ * the resource owner can keep host telemetry longer/shorter than
253
+ * the project default. Monitor / unknown resource types fall back
254
+ * to the project retention.
255
+ */
256
+ @CaptureSpan()
257
+ public static async buildResourceMetadataForNonService(data: {
258
+ serviceName: string;
259
+ resourceId: ObjectID;
260
+ serviceType: ServiceType;
261
+ projectId: ObjectID;
262
+ }): Promise<TelemetryServiceMetadata> {
263
+ const projectContext: ProjectRetentionContext =
264
+ await this.getProjectRetentionContext(data.projectId);
265
+
266
+ const resourceRetention: {
267
+ retainTelemetryDataForDays: number | null;
268
+ telemetryRetentionConfig: TelemetryRetentionConfig | null;
269
+ } = await this.getResourceRetention(data.resourceId, data.serviceType);
270
+
271
+ return {
272
+ serviceName: data.serviceName,
273
+ serviceId: data.resourceId,
274
+ serviceType: data.serviceType,
275
+ dataRententionInDays:
276
+ resourceRetention.retainTelemetryDataForDays ||
277
+ projectContext.projectRetentionInDays,
278
+ serviceRetentionConfig: resourceRetention.telemetryRetentionConfig,
279
+ serviceRetentionInDays: resourceRetention.retainTelemetryDataForDays,
280
+ projectRetentionConfig: projectContext.projectRetentionConfig,
281
+ projectRetentionInDays: projectContext.projectRetentionInDays,
282
+ };
283
+ }
284
+
285
+ /*
286
+ * Look up per-resource retention overrides for non-Service telemetry.
287
+ * One small SELECT per batch — the caller caches the resulting
288
+ * TelemetryServiceMetadata under `serviceDictionary[serviceName]`
289
+ * so steady-state ingest skips this lookup after the first row.
290
+ */
291
+ @CaptureSpan()
292
+ private static async getResourceRetention(
293
+ resourceId: ObjectID,
294
+ serviceType: ServiceType,
295
+ ): Promise<{
296
+ retainTelemetryDataForDays: number | null;
297
+ telemetryRetentionConfig: TelemetryRetentionConfig | null;
298
+ }> {
299
+ try {
300
+ if (serviceType === ServiceType.Host) {
301
+ const host: Host | null = await HostService.findOneById({
302
+ id: resourceId,
303
+ select: {
304
+ retainTelemetryDataForDays: true,
305
+ telemetryRetentionConfig: true,
306
+ },
307
+ props: { isRoot: true },
308
+ });
309
+ return {
310
+ retainTelemetryDataForDays: host?.retainTelemetryDataForDays ?? null,
311
+ telemetryRetentionConfig: host?.telemetryRetentionConfig ?? null,
312
+ };
313
+ }
314
+ if (serviceType === ServiceType.DockerHost) {
315
+ const dockerHost: DockerHost | null =
316
+ await DockerHostService.findOneById({
317
+ id: resourceId,
318
+ select: {
319
+ retainTelemetryDataForDays: true,
320
+ telemetryRetentionConfig: true,
321
+ },
322
+ props: { isRoot: true },
323
+ });
324
+ return {
325
+ retainTelemetryDataForDays:
326
+ dockerHost?.retainTelemetryDataForDays ?? null,
327
+ telemetryRetentionConfig:
328
+ dockerHost?.telemetryRetentionConfig ?? null,
329
+ };
330
+ }
331
+ if (serviceType === ServiceType.KubernetesCluster) {
332
+ const cluster: KubernetesCluster | null =
333
+ await KubernetesClusterService.findOneById({
334
+ id: resourceId,
335
+ select: {
336
+ retainTelemetryDataForDays: true,
337
+ telemetryRetentionConfig: true,
338
+ },
339
+ props: { isRoot: true },
340
+ });
341
+ return {
342
+ retainTelemetryDataForDays:
343
+ cluster?.retainTelemetryDataForDays ?? null,
344
+ telemetryRetentionConfig: cluster?.telemetryRetentionConfig ?? null,
345
+ };
346
+ }
347
+ } catch (err) {
348
+ logger.warn(
349
+ `Per-resource retention lookup failed for ${serviceType} ${resourceId.toString()}: ${
350
+ err instanceof Error ? err.message : String(err)
351
+ }`,
352
+ );
353
+ }
354
+ return {
355
+ retainTelemetryDataForDays: null,
356
+ telemetryRetentionConfig: null,
357
+ };
358
+ }
359
+
228
360
  @CaptureSpan()
229
361
  public static getMetricFromDatapoint(data: {
230
362
  dbMetric: Metric;
@@ -9,6 +9,7 @@ import AnalyticsTableName from "../../Types/AnalyticsDatabase/AnalyticsTableName
9
9
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
10
10
  import { DbJSONResponse, Results } from "./AnalyticsDatabaseService";
11
11
  import logger from "../Utils/Logger";
12
+ import ServiceType from "../../Types/Telemetry/ServiceType";
12
13
 
13
14
  export interface HistogramBucket {
14
15
  time: string;
@@ -69,6 +70,17 @@ export class TraceAggregationService {
69
70
  "statusCode",
70
71
  "isRootSpan",
71
72
  ]);
73
+ /*
74
+ * Virtual facet keys — same scheme as LogAggregationService. The
75
+ * `serviceId` slot is reused for host / docker host / k8s cluster ids,
76
+ * disambiguated by the `serviceType` discriminator.
77
+ */
78
+ private static readonly RESOURCE_FACET_KEYS: Map<string, ServiceType> =
79
+ new Map([
80
+ ["hostId", ServiceType.Host],
81
+ ["dockerHostId", ServiceType.DockerHost],
82
+ ["kubernetesClusterId", ServiceType.KubernetesCluster],
83
+ ]);
72
84
  private static readonly ATTRIBUTE_KEY_PATTERN: RegExp = /^[a-zA-Z0-9._:/-]+$/;
73
85
  private static readonly MAX_FACET_KEY_LENGTH: number = 256;
74
86
 
@@ -156,14 +168,25 @@ export class TraceAggregationService {
156
168
  TraceAggregationService.validateFacetKey(facetKey);
157
169
  }
158
170
 
171
+ const resourceKeys: Array<string> = request.facetKeys.filter(
172
+ (key: string): boolean => {
173
+ return TraceAggregationService.RESOURCE_FACET_KEYS.has(key);
174
+ },
175
+ );
159
176
  const topLevelKeys: Array<string> = request.facetKeys.filter(
160
177
  (key: string): boolean => {
161
- return TraceAggregationService.isTopLevelColumn(key);
178
+ return (
179
+ TraceAggregationService.isTopLevelColumn(key) &&
180
+ !TraceAggregationService.RESOURCE_FACET_KEYS.has(key)
181
+ );
162
182
  },
163
183
  );
164
184
  const attributeKeys: Array<string> = request.facetKeys.filter(
165
185
  (key: string): boolean => {
166
- return !TraceAggregationService.isTopLevelColumn(key);
186
+ return (
187
+ !TraceAggregationService.isTopLevelColumn(key) &&
188
+ !TraceAggregationService.RESOURCE_FACET_KEYS.has(key)
189
+ );
167
190
  },
168
191
  );
169
192
 
@@ -171,6 +194,13 @@ export class TraceAggregationService {
171
194
  if (topLevelKeys.length > 0) {
172
195
  selectColumns.push(...topLevelKeys);
173
196
  }
197
+ if (resourceKeys.length > 0) {
198
+ // Virtual facets read out of serviceId disambiguated by serviceType.
199
+ if (!selectColumns.includes("serviceId")) {
200
+ selectColumns.push("serviceId");
201
+ }
202
+ selectColumns.push("serviceType");
203
+ }
174
204
  if (attributeKeys.length > 0) {
175
205
  selectColumns.push("attributes");
176
206
  }
@@ -244,6 +274,27 @@ export class TraceAggregationService {
244
274
  map.set(value, (map.get(value) || 0) + 1);
245
275
  }
246
276
 
277
+ if (resourceKeys.length > 0) {
278
+ const rowServiceType: string =
279
+ row["serviceType"] === undefined || row["serviceType"] === null
280
+ ? ""
281
+ : String(row["serviceType"]);
282
+ const rowServiceId: unknown = row["serviceId"];
283
+ if (rowServiceId !== undefined && rowServiceId !== null) {
284
+ const value: string = String(rowServiceId);
285
+ if (value.length > 0) {
286
+ for (const key of resourceKeys) {
287
+ const expected: ServiceType | undefined =
288
+ TraceAggregationService.RESOURCE_FACET_KEYS.get(key);
289
+ if (expected && rowServiceType === expected) {
290
+ const map: Map<string, number> = counts[key]!;
291
+ map.set(value, (map.get(value) || 0) + 1);
292
+ }
293
+ }
294
+ }
295
+ }
296
+ }
297
+
247
298
  if (attributeKeys.length > 0) {
248
299
  const attrs: unknown = row["attributes"];
249
300
  let parsed: Record<string, unknown> | null = null;
@@ -368,13 +419,20 @@ export class TraceAggregationService {
368
419
 
369
420
  TraceAggregationService.validateFacetKey(request.facetKey);
370
421
 
371
- const isTopLevelColumn: boolean = TraceAggregationService.isTopLevelColumn(
372
- request.facetKey,
373
- );
422
+ const resourceServiceType: ServiceType | undefined =
423
+ TraceAggregationService.RESOURCE_FACET_KEYS.get(request.facetKey);
424
+ const isResourceFacet: boolean = resourceServiceType !== undefined;
425
+ const isTopLevelColumn: boolean =
426
+ isResourceFacet ||
427
+ TraceAggregationService.isTopLevelColumn(request.facetKey);
374
428
 
375
429
  const statement: Statement = new Statement();
376
430
 
377
- if (isTopLevelColumn) {
431
+ if (isResourceFacet) {
432
+ statement.append(
433
+ SQL`SELECT toString(serviceId) AS val, count() AS cnt FROM ${TraceAggregationService.TABLE_NAME}`,
434
+ );
435
+ } else if (isTopLevelColumn) {
378
436
  statement.append(
379
437
  SQL`SELECT toString(${request.facetKey}) AS val, count() AS cnt FROM ${TraceAggregationService.TABLE_NAME}`,
380
438
  );
@@ -400,7 +458,21 @@ export class TraceAggregationService {
400
458
  }}`,
401
459
  );
402
460
 
403
- if (!isTopLevelColumn) {
461
+ if (isResourceFacet) {
462
+ statement.append(
463
+ SQL` AND serviceType = ${{
464
+ type: TableColumnType.Text,
465
+ value: resourceServiceType as string,
466
+ }}`,
467
+ );
468
+ } else if (request.facetKey === "serviceId") {
469
+ statement.append(
470
+ SQL` AND (serviceType = '' OR serviceType = ${{
471
+ type: TableColumnType.Text,
472
+ value: ServiceType.OpenTelemetry as string,
473
+ }})`,
474
+ );
475
+ } else if (!isTopLevelColumn) {
404
476
  statement.append(
405
477
  SQL` AND JSONHas(attributes, ${{
406
478
  type: TableColumnType.Text,
@@ -539,7 +611,10 @@ export class TraceAggregationService {
539
611
  throw new BadDataException("Invalid facetKey");
540
612
  }
541
613
 
542
- if (TraceAggregationService.isTopLevelColumn(facetKey)) {
614
+ if (
615
+ TraceAggregationService.isTopLevelColumn(facetKey) ||
616
+ TraceAggregationService.RESOURCE_FACET_KEYS.has(facetKey)
617
+ ) {
543
618
  return;
544
619
  }
545
620