Merge pull request #5624 from Dokploy/feat/logs-and-metrics

feat(logs-and-metrics): ship container logs and metrics to external providers via Vector
This commit is contained in:
Yash Kumar
2026-10-15 00:08:48 +05:30
committed by GitHub
76 changed files with 18326 additions and 74 deletions
+2 -1
View File
@@ -94,4 +94,5 @@ export type AuditResourceType =
| "compose"
| "network"
| "vaultProvider"
| "dnsProvider";
| "dnsProvider"
| "telemetryProvider";
+1
View File
@@ -41,6 +41,7 @@ export * from "./shared";
export * from "./ssh-key";
export * from "./sso";
export * from "./tag";
export * from "./telemetry-provider";
export * from "./transfer";
export * from "./user";
export * from "./utils";
+5 -1
View File
@@ -1,4 +1,4 @@
import { relations } from "drizzle-orm";
import { relations, sql } from "drizzle-orm";
import {
boolean,
integer,
@@ -42,6 +42,10 @@ export const server = pgTable("server", {
.notNull()
.$defaultFn(() => generateAppName("server")),
enableDockerCleanup: boolean("enableDockerCleanup").notNull().default(false),
telemetryProviderIds: text("telemetryProviderIds")
.array()
.notNull()
.default(sql`ARRAY[]::text[]`),
buildsConcurrency: integer("buildsConcurrency").notNull().default(1),
createdAt: text("createdAt").notNull(),
organizationId: text("organizationId")
@@ -0,0 +1,90 @@
import { boolean, jsonb, pgEnum, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
import { organization } from "./account";
import { encryptedText } from "./utils";
export const telemetryProviderType = pgEnum("TelemetryProviderType", [
"loki",
"datadog",
"betterstack",
"elasticsearch",
"splunk_hec",
"aws_cloudwatch",
"prometheus_remote_write",
"new_relic",
"influxdb",
]);
export const telemetryProvider = pgTable("telemetryProvider", {
telemetryProviderId: text("telemetryProviderId")
.notNull()
.primaryKey()
.$defaultFn(() => nanoid()),
name: text("name").notNull(),
providerType: telemetryProviderType("providerType").notNull(),
signals: text("signals").array().notNull(),
endpoint: encryptedText("endpoint"),
apiKey: encryptedText("apiKey"),
apiSecret: encryptedText("apiSecret"),
extraConfig: jsonb("extraConfig").$type<Record<string, unknown>>(),
enabled: boolean("enabled").notNull().default(true),
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
organizationId: text("organizationId")
.notNull()
.references(() => organization.id, { onDelete: "cascade" }),
});
const createSchema = createInsertSchema(telemetryProvider, {
telemetryProviderId: z.string().min(1),
name: z.string().min(1),
providerType: z.enum(telemetryProviderType.enumValues),
signals: z.array(z.enum(["logs", "metrics"])).min(1),
endpoint: z
.string()
.min(1)
.refine((value) => !/\s/.test(value), {
message: "Endpoint cannot contain whitespace",
})
.nullable()
.optional(),
apiKey: z.string().min(1).nullable().optional(),
apiSecret: z.string().min(1).nullable().optional(),
extraConfig: z.record(z.string(), z.unknown()).nullable().optional(),
enabled: z.boolean().optional(),
organizationId: z.string().min(1),
});
export const apiCreateTelemetryProvider = createSchema
.pick({
name: true,
providerType: true,
signals: true,
endpoint: true,
apiKey: true,
apiSecret: true,
extraConfig: true,
enabled: true,
})
.required({ name: true, providerType: true, signals: true });
export const apiUpdateTelemetryProvider = apiCreateTelemetryProvider
.partial()
.extend({
telemetryProviderId: z.string().min(1),
});
export const apiRemoveTelemetryProvider = z.object({
telemetryProviderId: z.string().min(1),
});
export const apiFindOneTelemetryProvider = z.object({
telemetryProviderId: z.string().min(1),
});
export const apiTestTelemetryProvider = apiCreateTelemetryProvider.partial({
name: true,
});
@@ -1,4 +1,4 @@
import { relations } from "drizzle-orm";
import { relations, sql } from "drizzle-orm";
import {
boolean,
integer,
@@ -10,6 +10,7 @@ import {
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
import { organization } from "./account";
import { certificateType } from "./shared";
export const webServerSettings = pgTable("webServerSettings", {
@@ -26,6 +27,14 @@ export const webServerSettings = pgTable("webServerSettings", {
sshPrivateKey: text("sshPrivateKey"),
enableDockerCleanup: boolean("enableDockerCleanup").notNull().default(true),
logCleanupCron: text("logCleanupCron").default("0 0 * * *"),
telemetryProviderIds: text("telemetryProviderIds")
.array()
.notNull()
.default(sql`ARRAY[]::text[]`),
vectorAgentOrganizationId: text("vectorAgentOrganizationId").references(
() => organization.id,
{ onDelete: "set null" },
),
// Metrics Configuration
metricsConfig: jsonb("metricsConfig")
.$type<{
@@ -140,46 +149,52 @@ const createSchema = createInsertSchema(webServerSettings, {
id: z.string().min(1),
});
export const apiUpdateWebServerSettings = createSchema.partial().extend({
serverIp: z.string().optional(),
certificateType: z.enum(["letsencrypt", "none", "custom"]).optional(),
https: z.boolean().optional(),
host: z.string().optional(),
letsEncryptEmail: z.string().email().optional().nullable(),
sshPrivateKey: z.string().optional(),
enableDockerCleanup: z.boolean().optional(),
logCleanupCron: z.string().optional().nullable(),
metricsConfig: z
.object({
server: z.object({
type: z.enum(["Dokploy", "Remote"]),
refreshRate: z.number(),
port: z.number(),
token: z.string(),
urlCallback: z.string(),
retentionDays: z.number(),
cronJob: z.string(),
thresholds: z.object({
cpu: z.number(),
memory: z.number(),
export const apiUpdateWebServerSettings = createSchema
.partial()
.extend({
serverIp: z.string().optional(),
certificateType: z.enum(["letsencrypt", "none", "custom"]).optional(),
https: z.boolean().optional(),
host: z.string().optional(),
letsEncryptEmail: z.string().email().optional().nullable(),
sshPrivateKey: z.string().optional(),
enableDockerCleanup: z.boolean().optional(),
logCleanupCron: z.string().optional().nullable(),
metricsConfig: z
.object({
server: z.object({
type: z.enum(["Dokploy", "Remote"]),
refreshRate: z.number(),
port: z.number(),
token: z.string(),
urlCallback: z.string(),
retentionDays: z.number(),
cronJob: z.string(),
thresholds: z.object({
cpu: z.number(),
memory: z.number(),
}),
}),
}),
containers: z.object({
refreshRate: z.number(),
services: z.object({
include: z.array(z.string()),
exclude: z.array(z.string()),
containers: z.object({
refreshRate: z.number(),
services: z.object({
include: z.array(z.string()),
exclude: z.array(z.string()),
}),
}),
}),
})
.optional(),
cleanupCacheApplications: z.boolean().optional(),
cleanupCacheOnPreviews: z.boolean().optional(),
cleanupCacheOnCompose: z.boolean().optional(),
remoteServersOnly: z.boolean().optional(),
enforceSSO: z.boolean().optional(),
buildsConcurrency: z.number().int().min(1).max(100).optional(),
});
})
.optional(),
cleanupCacheApplications: z.boolean().optional(),
cleanupCacheOnPreviews: z.boolean().optional(),
cleanupCacheOnCompose: z.boolean().optional(),
remoteServersOnly: z.boolean().optional(),
enforceSSO: z.boolean().optional(),
buildsConcurrency: z.number().int().min(1).max(100).optional(),
})
.omit({
telemetryProviderIds: true,
vectorAgentOrganizationId: true,
});
export const apiUpdateWebServerBuildsConcurrency = z.object({
buildsConcurrency: z.number().int().min(1).max(100),