diff --git a/chart/values.yaml b/chart/values.yaml index e6f8dd984d..c4a2453d6e 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -326,9 +326,12 @@ components: metrics: expose: true containerPort: 9500 - debug: + debugNode: expose: false containerPort: 9229 + debug: + expose: false + containerPort: 6060 serviceSessionAffinity: None serverContainer: volumeMounts: null diff --git a/components/server/src/container-module.ts b/components/server/src/container-module.ts index 0de9d9372c..5d31494ab5 100644 --- a/components/server/src/container-module.ts +++ b/components/server/src/container-module.ts @@ -83,6 +83,7 @@ import { defaultGRPCOptions } from '@gitpod/gitpod-protocol/lib/util/grpc'; import { IDEConfigService } from './ide-config'; import { PrometheusClientCallMetrics } from "@gitpod/gitpod-protocol/lib/messaging/client-call-metrics"; import { IClientCallMetrics } from '@gitpod/content-service/lib/client-call-metrics'; +import { DebugApp } from './debug-app'; export const productionContainerModule = new ContainerModule((bind, unbind, isBound, rebind) => { bind(Config).toConstantValue(ConfigFile.fromFile()); @@ -103,6 +104,7 @@ export const productionContainerModule = new ContainerModule((bind, unbind, isBo bind(SessionHandlerProvider).toSelf().inSingletonScope(); bind(Server).toSelf().inSingletonScope(); + bind(DebugApp).toSelf().inSingletonScope(); bind(GitpodFileParser).toSelf().inSingletonScope(); diff --git a/components/server/src/debug-app.ts b/components/server/src/debug-app.ts new file mode 100644 index 0000000000..8e135815f4 --- /dev/null +++ b/components/server/src/debug-app.ts @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2021 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License-AGPL.txt in the project root for license information. + */ + +import * as http from 'http'; +import * as express from 'express'; +import { injectable, postConstruct } from "inversify"; +import { log, LogrusLogLevel } from '@gitpod/gitpod-protocol/lib/util/logging'; + + +export interface SetLogLevelRequest { + level: LogrusLogLevel, +} +export namespace SetLogLevelRequest { + export function is(o: any): o is SetLogLevelRequest { + return typeof o === 'object' + && 'level' in o; + } +} + +@injectable() +export class DebugApp { + protected _app: express.Application; + protected httpServer: http.Server | undefined = undefined; + + @postConstruct() + public ctor() { + this._app = this.create(); + } + + create(): express.Application { + const app = express(); + app.post('/debug/logging', (req, res) => { + try { + const parsed = JSON.parse(req.body); + if (!SetLogLevelRequest.is(parsed)) { + res.status(400).end("not a SetLogLevelRequest"); + return; + } + + const newLogLevel = parsed.level; + log.setLogLevel(newLogLevel); + log.info("set log level", { newLogLevel }); + } catch (err) { + res.status(500).end(err); + } + }); + return app; + } + + public start(port: number) { + this.httpServer = this._app.listen(port, 'localhost', () => { + log.info(`debug server listening on port: ${port}`); + }); + } + + public async stop() { + const server = this.httpServer; + if (!server) { + return; + } + return new Promise((resolve) => server.close((err: any) => { + if (err) { + log.warn(`error while closing http server`, { err }); + } + this.httpServer = undefined; + resolve(); + })); + } + + public get app(): express.Application { + return this._app; + } +} \ No newline at end of file diff --git a/components/server/src/server.ts b/components/server/src/server.ts index 189477a1cb..22c40b700e 100644 --- a/components/server/src/server.ts +++ b/components/server/src/server.ts @@ -41,6 +41,7 @@ import { OAuthController } from './oauth-server/oauth-controller'; import { HeadlessLogController, HEADLESS_LOGS_PATH_PREFIX, HEADLESS_LOG_DOWNLOAD_PATH_PREFIX } from './workspace/headless-log-controller'; import { NewsletterSubscriptionController } from './user/newsletter-subscription-controller'; import { Config } from './config'; +import { DebugApp } from './debug-app'; @injectable() export class Server { @@ -58,6 +59,7 @@ export class Server { @inject(MonitoringEndpointsApp) protected readonly monitoringEndpointsApp: MonitoringEndpointsApp; @inject(CodeSyncService) private readonly codeSyncService: CodeSyncService; @inject(HeadlessLogController) protected readonly headlessLogController: HeadlessLogController; + @inject(DebugApp) protected readonly debugApp: DebugApp; @inject(RabbitMQConsensusLeaderMessenger) protected readonly consensusMessenger: RabbitMQConsensusLeaderMessenger; @inject(ConsensusLeaderQorum) protected readonly qorum: ConsensusLeaderQorum; @@ -76,8 +78,8 @@ export class Server { protected readonly eventEmitter = new EventEmitter(); protected app?: express.Application; protected httpServer?: http.Server; - protected monApp?: express.Application; - protected monHttpServer?: http.Server; + protected monitoringApp?: express.Application; + protected monitoringHttpServer?: http.Server; public async init(app: express.Application) { log.setVersion(this.config.version); @@ -210,7 +212,7 @@ export class Server { // Health check + metrics endpoints - this.monApp = this.monitoringEndpointsApp.create(); + this.monitoringApp = this.monitoringEndpointsApp.create(); // Report current websocket connections this.installWebsocketConnectionGauge(); @@ -274,15 +276,18 @@ export class Server { }) this.httpServer = httpServer; - if (this.monApp) { - this.monHttpServer = this.monApp.listen(9500, 'localhost', () => { - log.info(`monitoring app listening on port: ${(this.monHttpServer!.address()).port}`); + if (this.monitoringApp) { + this.monitoringHttpServer = this.monitoringApp.listen(9500, 'localhost', () => { + log.info(`monitoring app listening on port: ${(this.monitoringHttpServer!.address()).port}`); }); } + + this.debugApp.start(6060); } public async stop() { - await this.stopServer(this.monHttpServer); + await this.debugApp.stop(); + await this.stopServer(this.monitoringHttpServer); await this.stopServer(this.httpServer); log.info('server stopped.'); }