@contember/engine-server 1.2.0-alpha.14 → 1.2.0-alpha.16
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/dist/src/MasterContainer.d.ts +6 -6
- package/dist/src/MasterContainer.d.ts.map +1 -1
- package/dist/src/MasterContainer.js +10 -13
- package/dist/src/MasterContainer.js.map +1 -1
- package/dist/src/bootstrap/Initializer.d.ts.map +1 -1
- package/dist/src/bootstrap/Initializer.js +3 -15
- package/dist/src/bootstrap/Initializer.js.map +1 -1
- package/dist/src/config/configSchema.d.ts +22 -0
- package/dist/src/config/configSchema.d.ts.map +1 -1
- package/dist/src/config/configSchema.js +13 -3
- package/dist/src/config/configSchema.js.map +1 -1
- package/dist/src/config/configTemplate.d.ts.map +1 -1
- package/dist/src/config/configTemplate.js +18 -0
- package/dist/src/config/configTemplate.js.map +1 -1
- package/dist/src/project/ProjectContainer.d.ts +8 -2
- package/dist/src/project/ProjectContainer.d.ts.map +1 -1
- package/dist/src/project/ProjectContainer.js +20 -5
- package/dist/src/project/ProjectContainer.js.map +1 -1
- package/dist/src/project/ProjectContainerResolver.d.ts +2 -3
- package/dist/src/project/ProjectContainerResolver.d.ts.map +1 -1
- package/dist/src/project/ProjectContainerResolver.js +3 -4
- package/dist/src/project/ProjectContainerResolver.js.map +1 -1
- package/dist/src/project/ProjectInitializer.d.ts +3 -2
- package/dist/src/project/ProjectInitializer.d.ts.map +1 -1
- package/dist/src/project/ProjectInitializer.js +3 -3
- package/dist/src/project/ProjectInitializer.js.map +1 -1
- package/dist/src/project/ProjectSchemaResolver.d.ts +2 -7
- package/dist/src/project/ProjectSchemaResolver.d.ts.map +1 -1
- package/dist/src/project/ProjectSchemaResolver.js +2 -16
- package/dist/src/project/ProjectSchemaResolver.js.map +1 -1
- package/dist/src/projectGroup/ProjectGroupContainer.d.ts +3 -1
- package/dist/src/projectGroup/ProjectGroupContainer.d.ts.map +1 -1
- package/dist/src/projectGroup/ProjectGroupContainer.js +8 -6
- package/dist/src/projectGroup/ProjectGroupContainer.js.map +1 -1
- package/dist/src/start.js +16 -13
- package/dist/src/start.js.map +1 -1
- package/dist/src/tsconfig.tsbuildinfo +1 -1
- package/dist/src/utils/sentry.d.ts +8 -9
- package/dist/src/utils/sentry.d.ts.map +1 -1
- package/dist/src/utils/sentry.js +44 -29
- package/dist/src/utils/sentry.js.map +1 -1
- package/dist/src/utils/serverStartup.d.ts +3 -1
- package/dist/src/utils/serverStartup.d.ts.map +1 -1
- package/dist/src/utils/serverStartup.js +47 -10
- package/dist/src/utils/serverStartup.js.map +1 -1
- package/dist/src/utils/serverTermination.d.ts +2 -1
- package/dist/src/utils/serverTermination.d.ts.map +1 -1
- package/dist/src/utils/serverTermination.js +3 -5
- package/dist/src/utils/serverTermination.js.map +1 -1
- package/dist/tests/tsconfig.tsbuildinfo +1 -1
- package/package.json +21 -20
- package/src/MasterContainer.ts +17 -15
- package/src/bootstrap/Initializer.ts +3 -16
- package/src/config/configSchema.ts +37 -23
- package/src/config/configTemplate.ts +18 -0
- package/src/project/ProjectContainer.ts +23 -4
- package/src/project/ProjectContainerResolver.ts +4 -4
- package/src/project/ProjectInitializer.ts +4 -3
- package/src/project/ProjectSchemaResolver.ts +1 -18
- package/src/projectGroup/ProjectGroupContainer.ts +11 -6
- package/src/start.ts +24 -17
- package/src/tsconfig.json +3 -0
- package/src/utils/sentry.ts +45 -35
- package/src/utils/serverStartup.ts +45 -9
- package/src/utils/serverTermination.ts +4 -5
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Logger } from '@contember/engine-common'
|
|
2
1
|
import { ProjectGroupContainer } from '@contember/engine-http'
|
|
3
2
|
|
|
4
3
|
export class Initializer {
|
|
@@ -7,34 +6,22 @@ export class Initializer {
|
|
|
7
6
|
) {}
|
|
8
7
|
|
|
9
8
|
public async initialize(): Promise<string[]> {
|
|
10
|
-
// eslint-disable-next-line no-console
|
|
11
|
-
const logger = new Logger(console.log)
|
|
12
|
-
logger.group('\nInitializing tenant database')
|
|
13
|
-
// todo
|
|
14
9
|
const groupContainer = await this.projectGroupContainer
|
|
15
|
-
|
|
10
|
+
const tenantMigrationsLogger = groupContainer.logger.child()
|
|
16
11
|
const tenantContainer = groupContainer.tenantContainer
|
|
17
|
-
await tenantContainer.migrationsRunner.run(
|
|
18
|
-
logger.groupEnd()
|
|
12
|
+
await tenantContainer.migrationsRunner.run(tenantMigrationsLogger)
|
|
19
13
|
|
|
20
14
|
const projects: string[] = []
|
|
21
15
|
|
|
22
16
|
const tenantProjects = await tenantContainer.projectManager.getProjects(tenantContainer.databaseContext)
|
|
23
17
|
for (const { slug: projectSlug } of tenantProjects) {
|
|
24
|
-
|
|
25
|
-
const projectContainer = await groupContainer.projectContainerResolver.getProjectContainer(projectSlug, {
|
|
26
|
-
alias: false,
|
|
27
|
-
logger,
|
|
28
|
-
})
|
|
18
|
+
const projectContainer = await groupContainer.projectContainerResolver.getProjectContainer(projectSlug, { alias: false })
|
|
29
19
|
if (!projectContainer) {
|
|
30
20
|
throw new Error()
|
|
31
21
|
}
|
|
32
22
|
const project = projectContainer.project
|
|
33
23
|
projects.push(project.slug)
|
|
34
|
-
logger.groupEnd()
|
|
35
24
|
}
|
|
36
|
-
// eslint-disable-next-line no-console
|
|
37
|
-
console.log('')
|
|
38
25
|
return projects
|
|
39
26
|
}
|
|
40
27
|
}
|
|
@@ -3,30 +3,33 @@ import { MailerOptions } from '@contember/engine-tenant-api'
|
|
|
3
3
|
import { upperCaseFirst } from '../utils'
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
Typesafe.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
Typesafe.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
maxAgeMs: Typesafe.number,
|
|
28
|
-
}),
|
|
6
|
+
const dbConfigRequired = {
|
|
7
|
+
host: Typesafe.string,
|
|
8
|
+
port: Typesafe.number,
|
|
9
|
+
user: Typesafe.string,
|
|
10
|
+
password: Typesafe.string,
|
|
11
|
+
database: Typesafe.string,
|
|
12
|
+
}
|
|
13
|
+
const dbConfigOptional = {
|
|
14
|
+
ssl: Typesafe.boolean,
|
|
15
|
+
queryTimeoutMs: Typesafe.number,
|
|
16
|
+
statementTimeoutMs: Typesafe.number,
|
|
17
|
+
connectionTimeoutMs: Typesafe.number,
|
|
18
|
+
pool: Typesafe.partial({
|
|
19
|
+
maxConnections: Typesafe.number,
|
|
20
|
+
maxConnecting: Typesafe.number,
|
|
21
|
+
maxIdle: Typesafe.number,
|
|
22
|
+
reconnectIntervalMs: Typesafe.number,
|
|
23
|
+
idleTimeoutMs: Typesafe.number,
|
|
24
|
+
acquireTimeoutMs: Typesafe.number,
|
|
25
|
+
maxUses: Typesafe.number,
|
|
26
|
+
maxAgeMs: Typesafe.number,
|
|
29
27
|
}),
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const dbConfigSchema = Typesafe.intersection(
|
|
31
|
+
Typesafe.object(dbConfigRequired),
|
|
32
|
+
Typesafe.partial(dbConfigOptional),
|
|
30
33
|
)
|
|
31
34
|
|
|
32
35
|
export const tenantConfigSchema = Typesafe.intersection(
|
|
@@ -91,6 +94,10 @@ export const stageConfig = Typesafe.record(
|
|
|
91
94
|
),
|
|
92
95
|
)
|
|
93
96
|
|
|
97
|
+
const readDbSchema = Typesafe.intersection(
|
|
98
|
+
Typesafe.partial(dbConfigRequired),
|
|
99
|
+
Typesafe.partial(dbConfigOptional),
|
|
100
|
+
)
|
|
94
101
|
export const projectConfigSchema = Typesafe.object({
|
|
95
102
|
name: Typesafe.union(
|
|
96
103
|
Typesafe.string,
|
|
@@ -107,6 +114,13 @@ export const projectConfigSchema = Typesafe.object({
|
|
|
107
114
|
),
|
|
108
115
|
Typesafe.partial({
|
|
109
116
|
systemSchema: Typesafe.string,
|
|
117
|
+
read: (val: unknown) => {
|
|
118
|
+
const readDb = readDbSchema(val)
|
|
119
|
+
if ('host' in readDb) {
|
|
120
|
+
return readDb
|
|
121
|
+
}
|
|
122
|
+
return undefined
|
|
123
|
+
},
|
|
110
124
|
}),
|
|
111
125
|
),
|
|
112
126
|
})
|
|
@@ -58,6 +58,24 @@ export const configTemplate: any = {
|
|
|
58
58
|
maxUses: '%?project.env.DB_POOL_MAX_USES%',
|
|
59
59
|
maxAgeMs: '%?project.env.DB_POOL_MAX_AGE_MS%',
|
|
60
60
|
},
|
|
61
|
+
read: {
|
|
62
|
+
host: `%?project.env.DB_READ_HOST%`,
|
|
63
|
+
port: `%?project.env.DB_READ_PORT::number%`,
|
|
64
|
+
user: `%?project.env.DB_READ_USER%`,
|
|
65
|
+
password: `%?project.secret.db.read.password||project.env.DB_READ_PASSWORD%`,
|
|
66
|
+
database: `%?project.env.DB_READ_NAME%`,
|
|
67
|
+
ssl: `%?project.env.DB_READ_SSL::bool%`,
|
|
68
|
+
pool: {
|
|
69
|
+
maxConnections: '%?project.env.DB_READ_POOL_MAX_CONNECTIONS%',
|
|
70
|
+
maxConnecting: '%?project.env.DB_READ_POOL_MAX_CONNECTING%',
|
|
71
|
+
maxIdle: '%?project.env.DB_READ_POOL_MAX_IDLE%',
|
|
72
|
+
reconnectIntervalMs: '%?project.env.DB_READ_POOL_RECONNECT_INTERVAL_MS%',
|
|
73
|
+
idleTimeoutMs: '%?project.env.DB_READ_POOL_IDLE_TIMEOUT_MS%',
|
|
74
|
+
acquireTimeoutMs: '%?project.env.DB_READ_POOL_ACQUIRE_TIMEOUT_MS%',
|
|
75
|
+
maxUses: '%?project.env.DB_READ_POOL_MAX_USES%',
|
|
76
|
+
maxAgeMs: '%?project.env.DB_READ_POOL_MAX_AGE_MS%',
|
|
77
|
+
},
|
|
78
|
+
},
|
|
61
79
|
},
|
|
62
80
|
},
|
|
63
81
|
server: {
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
ProjectContainer,
|
|
11
11
|
Providers,
|
|
12
12
|
} from '@contember/engine-http'
|
|
13
|
+
import { Logger } from '@contember/logger'
|
|
13
14
|
|
|
14
15
|
export class ProjectContainerFactoryFactory {
|
|
15
16
|
constructor(
|
|
@@ -19,8 +20,8 @@ export class ProjectContainerFactoryFactory {
|
|
|
19
20
|
) {
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
create(schemaVersionBuilder: SchemaVersionBuilder): ProjectContainerFactory {
|
|
23
|
-
return new ProjectContainerFactory(this.debug, this.plugins, schemaVersionBuilder, this.providers)
|
|
23
|
+
create(schemaVersionBuilder: SchemaVersionBuilder, logger: Logger): ProjectContainerFactory {
|
|
24
|
+
return new ProjectContainerFactory(this.debug, this.plugins, schemaVersionBuilder, this.providers, logger)
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
27
|
|
|
@@ -34,6 +35,7 @@ export class ProjectContainerFactory {
|
|
|
34
35
|
private readonly plugins: Plugin[],
|
|
35
36
|
private readonly schemaVersionBuilder: SchemaVersionBuilder,
|
|
36
37
|
private readonly providers: Providers,
|
|
38
|
+
private readonly logger: Logger,
|
|
37
39
|
) {}
|
|
38
40
|
|
|
39
41
|
public createContainer(args: ProjectContainerFactoryArgs): ProjectContainer {
|
|
@@ -42,9 +44,11 @@ export class ProjectContainerFactory {
|
|
|
42
44
|
.pick(
|
|
43
45
|
'project',
|
|
44
46
|
'connection',
|
|
47
|
+
'readConnection',
|
|
45
48
|
'systemDatabaseContextFactory',
|
|
46
49
|
'contentSchemaResolver',
|
|
47
50
|
'graphQlSchemaFactory',
|
|
51
|
+
'logger',
|
|
48
52
|
)
|
|
49
53
|
}
|
|
50
54
|
|
|
@@ -54,10 +58,25 @@ export class ProjectContainerFactory {
|
|
|
54
58
|
this.providers)
|
|
55
59
|
.addService('schemaVersionBuilder', () =>
|
|
56
60
|
this.schemaVersionBuilder)
|
|
61
|
+
.addService('logger', () =>
|
|
62
|
+
this.logger.child({ project: project.slug }))
|
|
57
63
|
.addService('project', () =>
|
|
58
64
|
project)
|
|
59
|
-
.addService('connection', ({ project }) =>
|
|
60
|
-
Connection.create(project.db))
|
|
65
|
+
.addService('connection', ({ project, logger }) =>
|
|
66
|
+
Connection.create(project.db, err => logger.error(err)))
|
|
67
|
+
.addService('readConnection', ({ project, logger, connection }) => {
|
|
68
|
+
if (!project.db.read) {
|
|
69
|
+
return connection
|
|
70
|
+
}
|
|
71
|
+
return Connection.create({
|
|
72
|
+
...project.db,
|
|
73
|
+
...project.db.read,
|
|
74
|
+
pool: {
|
|
75
|
+
...project.db.pool,
|
|
76
|
+
...project.db.read.pool,
|
|
77
|
+
},
|
|
78
|
+
}, err => logger.error(err))
|
|
79
|
+
})
|
|
61
80
|
.addService('graphQlSchemaBuilderFactory', () =>
|
|
62
81
|
new GraphQlSchemaBuilderFactory())
|
|
63
82
|
.addService('permissionsByIdentityFactory', ({}) =>
|
|
@@ -3,9 +3,9 @@ import { ProjectContainer } from '@contember/engine-http'
|
|
|
3
3
|
import { DatabaseContext, ProjectManager, ProjectWithSecrets } from '@contember/engine-tenant-api'
|
|
4
4
|
import { ProjectInitializer as SystemProjectInitializer } from '@contember/engine-system-api'
|
|
5
5
|
import { ProjectContainerStore } from './ProjectContainerStore'
|
|
6
|
-
import { Logger } from '@contember/engine-common'
|
|
7
6
|
import { ProjectConfigResolver } from '../config/projectConfigResolver'
|
|
8
7
|
import { TenantConfig } from '../config/config'
|
|
8
|
+
import { Logger } from '@contember/logger'
|
|
9
9
|
|
|
10
10
|
export class ProjectContainerResolver {
|
|
11
11
|
private projectContainers = new ProjectContainerStore()
|
|
@@ -22,7 +22,7 @@ export class ProjectContainerResolver {
|
|
|
22
22
|
) {}
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
public async getProjectContainer(slug: string, { alias = false
|
|
25
|
+
public async getProjectContainer(slug: string, { alias = false }: { alias?: boolean } = {}): Promise<ProjectContainer | undefined> {
|
|
26
26
|
const realSlug = this.projectContainers.resolveAlias(slug)
|
|
27
27
|
if (realSlug) {
|
|
28
28
|
slug = realSlug
|
|
@@ -43,7 +43,7 @@ export class ProjectContainerResolver {
|
|
|
43
43
|
if (!project) {
|
|
44
44
|
return undefined
|
|
45
45
|
}
|
|
46
|
-
const container = await this.createProjectContainer(project
|
|
46
|
+
const container = await this.createProjectContainer(project)
|
|
47
47
|
if (slug !== project.slug) {
|
|
48
48
|
this.projectContainers.setAlias(project.slug, slug)
|
|
49
49
|
}
|
|
@@ -59,7 +59,7 @@ export class ProjectContainerResolver {
|
|
|
59
59
|
await this.systemProjectInitializer.initialize(
|
|
60
60
|
projectContainer.systemDatabaseContextFactory,
|
|
61
61
|
projectContainer.project,
|
|
62
|
-
logger ??
|
|
62
|
+
logger ?? projectContainer.logger,
|
|
63
63
|
)
|
|
64
64
|
const cleanups = this.onCreate.map(it => it(projectContainer) || (() => null))
|
|
65
65
|
return {
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { ProjectInitializer as ProjectInitializerInterface, ProjectWithSecrets } from '@contember/engine-tenant-api'
|
|
2
2
|
import { ProjectContainerResolver } from './ProjectContainerResolver'
|
|
3
|
+
import { Logger } from '@contember/logger'
|
|
3
4
|
|
|
4
5
|
export class ProjectInitializer implements ProjectInitializerInterface {
|
|
5
6
|
constructor(
|
|
6
7
|
private readonly projectContainerResolver: ProjectContainerResolver,
|
|
7
8
|
) {}
|
|
8
9
|
|
|
9
|
-
async initializeProject(project: ProjectWithSecrets) {
|
|
10
|
+
async initializeProject(project: ProjectWithSecrets, logger: Logger) {
|
|
10
11
|
await this.projectContainerResolver.createProjectContainer(project)
|
|
11
12
|
}
|
|
12
13
|
}
|
|
@@ -18,10 +19,10 @@ export class ProjectInitializerProxy implements ProjectInitializerInterface {
|
|
|
18
19
|
this.initializer = initializer
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
initializeProject(project: ProjectWithSecrets) {
|
|
22
|
+
initializeProject(project: ProjectWithSecrets, logger: Logger) {
|
|
22
23
|
if (!this.initializer) {
|
|
23
24
|
throw new Error('Initializer is not set')
|
|
24
25
|
}
|
|
25
|
-
return this.initializer.initializeProject(project)
|
|
26
|
+
return this.initializer.initializeProject(project, logger)
|
|
26
27
|
}
|
|
27
28
|
}
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import { ProjectSchemaResolver as ProjectSchemaResolverInterface } from '@contember/engine-tenant-api'
|
|
2
|
-
import { SchemaVersionBuilder, VersionedSchema } from '@contember/engine-system-api'
|
|
3
2
|
import { ProjectContainerResolver } from '@contember/engine-http'
|
|
4
3
|
import { Schema } from '@contember/schema'
|
|
5
4
|
|
|
6
5
|
export class ProjectSchemaResolver implements ProjectSchemaResolverInterface {
|
|
7
|
-
private schemaCache = new Map<string, VersionedSchema>()
|
|
8
|
-
|
|
9
6
|
constructor(
|
|
10
7
|
private readonly projectContainerResolver: ProjectContainerResolver,
|
|
11
|
-
private readonly schemaVersionBuilder: SchemaVersionBuilder,
|
|
12
8
|
) {}
|
|
13
9
|
|
|
14
10
|
async getSchema(slug: string) {
|
|
@@ -17,16 +13,7 @@ export class ProjectSchemaResolver implements ProjectSchemaResolverInterface {
|
|
|
17
13
|
return undefined
|
|
18
14
|
}
|
|
19
15
|
const db = container.systemDatabaseContextFactory.create()
|
|
20
|
-
|
|
21
|
-
const newSchema = await this.schemaVersionBuilder.buildSchema(db, cachedSchema)
|
|
22
|
-
if (cachedSchema !== newSchema) {
|
|
23
|
-
this.schemaCache.set(slug, newSchema)
|
|
24
|
-
}
|
|
25
|
-
return newSchema
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
clearCache() {
|
|
29
|
-
this.schemaCache.clear()
|
|
16
|
+
return container.contentSchemaResolver.getSchema(db)
|
|
30
17
|
}
|
|
31
18
|
}
|
|
32
19
|
|
|
@@ -43,8 +30,4 @@ export class ProjectSchemaResolverProxy implements ProjectSchemaResolverInterfac
|
|
|
43
30
|
}
|
|
44
31
|
return this.resolver.getSchema(projectSlug)
|
|
45
32
|
}
|
|
46
|
-
|
|
47
|
-
clearCache() {
|
|
48
|
-
this.resolver?.clearCache()
|
|
49
|
-
}
|
|
50
33
|
}
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
import { SystemContainerFactory } from '@contember/engine-system-api'
|
|
24
24
|
import { ProjectConfigResolver } from '../config/projectConfigResolver'
|
|
25
25
|
import { createSecretKey } from 'crypto'
|
|
26
|
+
import { Logger } from '@contember/logger'
|
|
26
27
|
|
|
27
28
|
interface ProjectGroupContainerFactoryArgs
|
|
28
29
|
{
|
|
@@ -40,18 +41,21 @@ export class ProjectGroupContainerFactory {
|
|
|
40
41
|
private readonly projectConfigResolver: ProjectConfigResolver,
|
|
41
42
|
private readonly tenantGraphQLHandlerFactory: TenantGraphQLHandlerFactory,
|
|
42
43
|
private readonly systemGraphQLHandlerFactory: SystemGraphQLHandlerFactory,
|
|
44
|
+
private readonly logger: Logger,
|
|
43
45
|
) {}
|
|
44
46
|
|
|
45
47
|
public create({ config, slug }: ProjectGroupContainerFactoryArgs): ProjectGroupContainer {
|
|
46
48
|
return new Builder({})
|
|
47
49
|
.addService('slug', () =>
|
|
48
50
|
slug)
|
|
51
|
+
.addService('logger', ({ slug }) =>
|
|
52
|
+
this.logger.child({ projectGroup: slug }))
|
|
49
53
|
.addService('providers', () =>
|
|
50
54
|
this.providers)
|
|
51
55
|
.addService('tenantDbCredentials', () =>
|
|
52
56
|
config.db)
|
|
53
|
-
.addService('tenantConnection', ({ tenantDbCredentials }): Connection.ConnectionType =>
|
|
54
|
-
Connection.create(tenantDbCredentials))
|
|
57
|
+
.addService('tenantConnection', ({ tenantDbCredentials, logger }): Connection.ConnectionType =>
|
|
58
|
+
Connection.create(tenantDbCredentials, err => logger.error(err)))
|
|
55
59
|
.addService('projectSchemaResolver', () =>
|
|
56
60
|
new ProjectSchemaResolverProxy())
|
|
57
61
|
.addService('projectInitializer', () =>
|
|
@@ -83,8 +87,8 @@ export class ProjectGroupContainerFactory {
|
|
|
83
87
|
this.systemContainerFactory.create({ identityFetcher }))
|
|
84
88
|
.addService('schemaVersionBuilder', ({ systemContainer }) =>
|
|
85
89
|
systemContainer.schemaVersionBuilder)
|
|
86
|
-
.addService('projectContainerFactory', ({ schemaVersionBuilder }) =>
|
|
87
|
-
this.projectContainerFactoryFactory.create(schemaVersionBuilder))
|
|
90
|
+
.addService('projectContainerFactory', ({ schemaVersionBuilder, logger }) =>
|
|
91
|
+
this.projectContainerFactoryFactory.create(schemaVersionBuilder, logger))
|
|
88
92
|
.addService('tenantProjectManager', ({ tenantContainer }) =>
|
|
89
93
|
tenantContainer.projectManager)
|
|
90
94
|
.addService('projectContainerResolver', ({ projectContainerFactory, tenantProjectManager, systemContainer, tenantContainer }) =>
|
|
@@ -97,8 +101,8 @@ export class ProjectGroupContainerFactory {
|
|
|
97
101
|
config,
|
|
98
102
|
))
|
|
99
103
|
|
|
100
|
-
.setupService('projectSchemaResolver', (it, { projectContainerResolver
|
|
101
|
-
it.setResolver(new ProjectSchemaResolver(projectContainerResolver
|
|
104
|
+
.setupService('projectSchemaResolver', (it, { projectContainerResolver }) => {
|
|
105
|
+
it.setResolver(new ProjectSchemaResolver(projectContainerResolver))
|
|
102
106
|
})
|
|
103
107
|
.setupService('projectInitializer', (it, { projectContainerResolver }) => {
|
|
104
108
|
it.setInitializer(new ProjectInitializer(projectContainerResolver))
|
|
@@ -124,6 +128,7 @@ export class ProjectGroupContainerFactory {
|
|
|
124
128
|
'projectMembershipResolver',
|
|
125
129
|
'tenantGraphQLHandler',
|
|
126
130
|
'slug',
|
|
131
|
+
'logger',
|
|
127
132
|
)
|
|
128
133
|
}
|
|
129
134
|
}
|
package/src/start.ts
CHANGED
|
@@ -1,28 +1,37 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
createContainer,
|
|
5
|
+
createDefaultLogger, createSentryLoggerHandler,
|
|
6
|
+
listenOnProcessTermination,
|
|
7
|
+
serverConfigSchema,
|
|
8
|
+
TerminationJob,
|
|
9
|
+
} from './index'
|
|
4
10
|
import loadPlugins from './loadPlugins'
|
|
5
|
-
import { initSentry } from './utils'
|
|
6
|
-
|
|
7
|
-
|
|
8
11
|
import { getServerVersion, isDebugMode, printStartInfo, resolveServerConfig } from './utils/serverStartup'
|
|
9
12
|
|
|
10
|
-
|
|
13
|
+
const logger = createDefaultLogger()
|
|
14
|
+
process.on('warning', message => {
|
|
15
|
+
logger.warn(message)
|
|
16
|
+
})
|
|
17
|
+
;(async () => {
|
|
18
|
+
const isDebug = isDebugMode()
|
|
11
19
|
if (process.env.NODE_HEAPDUMP === 'true') {
|
|
12
|
-
|
|
13
|
-
console.log('Initializing heapdump')
|
|
20
|
+
logger.info('Initializing heapdump')
|
|
14
21
|
await import('heapdump')
|
|
15
22
|
}
|
|
16
23
|
const terminationJobs: TerminationJob[] = []
|
|
17
|
-
listenOnProcessTermination(terminationJobs)
|
|
24
|
+
listenOnProcessTermination(terminationJobs, logger)
|
|
18
25
|
|
|
19
|
-
const isDebug = isDebugMode()
|
|
20
26
|
const version = getServerVersion()
|
|
21
|
-
printStartInfo({ version, isDebug })
|
|
27
|
+
printStartInfo({ version, isDebug }, logger)
|
|
22
28
|
const plugins = await loadPlugins()
|
|
23
29
|
const { serverConfig, projectConfigResolver, tenantConfigResolver } = await resolveServerConfig({ plugins, serverConfigSchema })
|
|
24
30
|
|
|
25
|
-
|
|
31
|
+
const sentryTransport = createSentryLoggerHandler(serverConfig.logging.sentry?.dsn)
|
|
32
|
+
if (sentryTransport !== null) {
|
|
33
|
+
logger.addHandler(sentryTransport)
|
|
34
|
+
}
|
|
26
35
|
|
|
27
36
|
const container = createContainer({
|
|
28
37
|
debugMode: isDebug,
|
|
@@ -31,19 +40,17 @@ import { getServerVersion, isDebugMode, printStartInfo, resolveServerConfig } fr
|
|
|
31
40
|
tenantConfigResolver,
|
|
32
41
|
plugins,
|
|
33
42
|
version,
|
|
43
|
+
logger,
|
|
34
44
|
})
|
|
35
45
|
|
|
36
46
|
const initializedProjects: string[] = await container.initializer.initialize()
|
|
37
47
|
|
|
38
48
|
const httpServer = container.koa.listen(serverConfig.port, () => {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
// eslint-disable-next-line no-console
|
|
42
|
-
console.log(initializedProjects.length ? `Initialized projects: ${initializedProjects.join(', ')}` : 'No project initialized')
|
|
49
|
+
logger.info(`Contember API running on http://localhost:${(serverConfig.port)}`)
|
|
50
|
+
logger.info(initializedProjects.length ? `Initialized projects: ${initializedProjects.join(', ')}` : 'No project initialized')
|
|
43
51
|
})
|
|
44
52
|
terminationJobs.push(() => new Promise<any>(resolve => httpServer.close(resolve)))
|
|
45
53
|
})().catch(e => {
|
|
46
|
-
|
|
47
|
-
console.log(e)
|
|
54
|
+
logger.crit(e)
|
|
48
55
|
process.exit(1)
|
|
49
56
|
})
|
package/src/tsconfig.json
CHANGED
package/src/utils/sentry.ts
CHANGED
|
@@ -1,46 +1,56 @@
|
|
|
1
1
|
import * as Sentry from '@sentry/node'
|
|
2
|
+
import { LoggerRequestSymbol } from '@contember/engine-http'
|
|
3
|
+
import { LogEntry, LoggerHandler, LogLevel, LogLevels } from '@contember/logger'
|
|
2
4
|
|
|
3
|
-
export
|
|
5
|
+
export class SentryLoggerHandler implements LoggerHandler {
|
|
6
|
+
constructor(
|
|
7
|
+
private readonly logLevel: LogLevel,
|
|
8
|
+
) {
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
getMinLevel(): number {
|
|
12
|
+
return this.logLevel.value
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
handle(entry: LogEntry) {
|
|
16
|
+
if (!entry.error || entry.level.value < this.logLevel.value) {
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
Sentry.captureException(entry.error, scope => {
|
|
20
|
+
scope.setTag('project', entry.loggerAttributes.project ?? entry.ownAttributes.project)
|
|
21
|
+
scope.setTag('module', entry.loggerAttributes.module ?? entry.ownAttributes.module)
|
|
22
|
+
scope.setLevel('error')
|
|
23
|
+
scope.setUser({
|
|
24
|
+
id: entry.loggerAttributes.user ?? entry.ownAttributes.user,
|
|
25
|
+
})
|
|
26
|
+
scope.setExtra('requestId', entry.loggerAttributes.requestId)
|
|
27
|
+
|
|
28
|
+
scope.addEventProcessor(event => {
|
|
29
|
+
return {
|
|
30
|
+
...event,
|
|
31
|
+
request: {
|
|
32
|
+
url: entry.loggerAttributes.url ?? entry.ownAttributes.url,
|
|
33
|
+
data: entry.loggerAttributes[LoggerRequestSymbol]?.body,
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
return scope
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
close(): void {
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const createSentryLoggerHandler = (dsn?: string): null | LoggerHandler => {
|
|
4
46
|
if (!dsn) {
|
|
5
|
-
return
|
|
47
|
+
return null
|
|
6
48
|
}
|
|
7
|
-
// eslint-disable-next-line no-console
|
|
8
|
-
console.log(`Intializing sentry with dsn: ${dsn}`)
|
|
9
49
|
Sentry.init({
|
|
10
50
|
dsn: dsn,
|
|
11
51
|
integrations: integrations => {
|
|
12
52
|
return integrations.filter(integration => integration.name !== 'Console' && integration.name !== 'Http')
|
|
13
53
|
},
|
|
14
54
|
})
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
interface SentryErrorContext {
|
|
18
|
-
user: string
|
|
19
|
-
body: string
|
|
20
|
-
project?: string
|
|
21
|
-
url: string
|
|
22
|
-
module: string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export const logSentryError = (error: any, context: SentryErrorContext) => {
|
|
26
|
-
Sentry.withScope(scope => {
|
|
27
|
-
if (context.project) {
|
|
28
|
-
scope.setTag('project', context.project)
|
|
29
|
-
}
|
|
30
|
-
scope.setTag('module', context.module)
|
|
31
|
-
scope.setLevel('error')
|
|
32
|
-
scope.setUser({
|
|
33
|
-
id: context.user,
|
|
34
|
-
})
|
|
35
|
-
scope.addEventProcessor(event => {
|
|
36
|
-
return {
|
|
37
|
-
...event,
|
|
38
|
-
request: {
|
|
39
|
-
url: context.url,
|
|
40
|
-
data: context.body,
|
|
41
|
-
},
|
|
42
|
-
}
|
|
43
|
-
})
|
|
44
|
-
Sentry.captureException(error)
|
|
45
|
-
})
|
|
55
|
+
return new SentryLoggerHandler(LogLevels.warn)
|
|
46
56
|
}
|
|
@@ -2,6 +2,7 @@ import { join } from 'path'
|
|
|
2
2
|
import { ConfigProcessor, Plugin } from '@contember/engine-plugins'
|
|
3
3
|
import { ConfigSource, readConfig, ServerConfig } from '../config/config'
|
|
4
4
|
import { Type } from '@contember/typesafe'
|
|
5
|
+
import { createLogger, JsonStreamLoggerHandler, Logger, LogLevels, PrettyPrintLoggerHandler } from '@contember/logger'
|
|
5
6
|
|
|
6
7
|
export const getServerVersion = (): string => {
|
|
7
8
|
const packageJsonFile = process.env.CONTEMBER_PACKAGE_JSON || join(__dirname, '../../../package.json')
|
|
@@ -12,16 +13,12 @@ export const getServerVersion = (): string => {
|
|
|
12
13
|
|
|
13
14
|
export const isDebugMode = (): boolean => process.env.NODE_ENV === 'development'
|
|
14
15
|
|
|
15
|
-
export const printStartInfo = ({ version, isDebug }: { version: string; isDebug: boolean }) => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
// eslint-disable-next-line no-console
|
|
19
|
-
console.log(`NODE_ENV is set to ${process.env.NODE_ENV}`)
|
|
16
|
+
export const printStartInfo = ({ version, isDebug }: { version: string; isDebug: boolean }, logger: Logger) => {
|
|
17
|
+
logger.info(`Starting Contember ${version}`)
|
|
18
|
+
logger.info(`NODE_ENV is set to ${process.env.NODE_ENV}`)
|
|
20
19
|
if (isDebug) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
// eslint-disable-next-line no-console
|
|
24
|
-
console.log('NEVER USE debug mode in production environment')
|
|
20
|
+
logger.warn('Starting Contember in debug mode')
|
|
21
|
+
logger.warn('NEVER USE debug mode in production environment')
|
|
25
22
|
}
|
|
26
23
|
}
|
|
27
24
|
|
|
@@ -40,3 +37,42 @@ export const resolveServerConfig = async <T extends ServerConfig>({ plugins, ser
|
|
|
40
37
|
|
|
41
38
|
return await readConfig(configSources, configProcessors, serverConfigSchema)
|
|
42
39
|
}
|
|
40
|
+
|
|
41
|
+
export const createDefaultLogger = (): Logger => {
|
|
42
|
+
const isDebug = isDebugMode()
|
|
43
|
+
const loggerType = process.env.CONTEMBER_LOGGER_FORMAT ?? (isDebug ? 'pretty' : 'json')
|
|
44
|
+
const stream = process.stderr
|
|
45
|
+
const handler = loggerType === 'pretty'
|
|
46
|
+
? new PrettyPrintLoggerHandler(stream, {
|
|
47
|
+
formatters: {
|
|
48
|
+
pid: () => undefined,
|
|
49
|
+
loggerId: () => undefined,
|
|
50
|
+
uri: ({ attributes, chalk }) => {
|
|
51
|
+
const color = attributes.status === undefined
|
|
52
|
+
? chalk.cyan
|
|
53
|
+
: attributes.status < 300
|
|
54
|
+
? chalk.green
|
|
55
|
+
: chalk.yellowBright
|
|
56
|
+
const timeInfo = attributes.totalTimeMs !== undefined ? ` ${attributes.totalTimeMs}ms` : ''
|
|
57
|
+
const statusInfo = attributes.status !== undefined ? ` [${attributes.status}]` : ''
|
|
58
|
+
return { line: color(`${attributes.method}${statusInfo} ${attributes.uri}${timeInfo} ${chalk.white.dim('#' + attributes.requestId)}`) }
|
|
59
|
+
},
|
|
60
|
+
method: () => undefined,
|
|
61
|
+
requestId: () => undefined,
|
|
62
|
+
timeLabel: () => undefined,
|
|
63
|
+
status: () => undefined,
|
|
64
|
+
totalTimeMs: () => undefined,
|
|
65
|
+
events: ({ formattedValue }) => formattedValue === '[]' ? undefined : formattedValue,
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
: new JsonStreamLoggerHandler(stream)
|
|
69
|
+
const logLevel = (process.env.CONTEMBER_LOGGER_LEVEL as keyof typeof LogLevels) ?? 'info'
|
|
70
|
+
return createLogger(handler, {
|
|
71
|
+
pid: process.pid,
|
|
72
|
+
loggerId: Math.random().toString().substring(2),
|
|
73
|
+
}, {
|
|
74
|
+
fingerCrossedOptions: {
|
|
75
|
+
logAlwaysLevel: LogLevels[logLevel],
|
|
76
|
+
},
|
|
77
|
+
})
|
|
78
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import cluster from 'cluster'
|
|
2
|
+
import { Logger } from '@contember/logger'
|
|
2
3
|
|
|
3
4
|
const signals = {
|
|
4
5
|
SIGHUP: 1,
|
|
@@ -6,14 +7,12 @@ const signals = {
|
|
|
6
7
|
SIGTERM: 15,
|
|
7
8
|
}
|
|
8
9
|
export type TerminationJob = (args: { signal: keyof typeof signals; code: number }) => Promise<void>
|
|
9
|
-
export const listenOnProcessTermination = (jobs: TerminationJob[]) => {
|
|
10
|
+
export const listenOnProcessTermination = (jobs: TerminationJob[], logger: Logger) => {
|
|
10
11
|
for (const [signal, code] of Object.entries(signals)) {
|
|
11
12
|
process.on(signal, async () => {
|
|
12
|
-
|
|
13
|
-
console.log(`Process ${process.pid} received a ${signal} signal, executing ${jobs.length} termination jobs`)
|
|
13
|
+
logger.info(`Process ${process.pid} received a ${signal} signal, executing ${jobs.length} termination jobs`)
|
|
14
14
|
await Promise.allSettled(jobs.map(it => it({ signal: signal as keyof typeof signals, code })))
|
|
15
|
-
|
|
16
|
-
console.log(cluster.isMaster ? `All terminated, exiting` : 'All terminated, exiting a worker')
|
|
15
|
+
logger.info(cluster.isMaster ? `All terminated, exiting` : 'All terminated, exiting a worker')
|
|
17
16
|
process.exit(128 + code)
|
|
18
17
|
})
|
|
19
18
|
}
|