Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

imprv: Set the service instance id after DB is initialized #9542

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f0188e4
devide getGrowiInfo as a service
yuki-takei Jan 7, 2025
6c2cb13
refactor getGrowiInfo
yuki-takei Jan 7, 2025
2574536
Merge branch 'feat/opentelemetry' into imprv/generate-site-url-hashed…
yuki-takei Jan 8, 2025
4863fed
upgrade opentelemetry packages
yuki-takei Jan 8, 2025
027a3d4
implement initServiceInstanceId
yuki-takei Jan 8, 2025
8588af5
get GROWI version fron getGrowiVersion()
yuki-takei Jan 8, 2025
e8a04ae
fix lint errors
yuki-takei Jan 8, 2025
ed87f47
instanciate at first
yuki-takei Jan 9, 2025
cc21af7
add 'app:serviceInstanceId' key to config
yuki-takei Jan 9, 2025
496db0d
generate service instance id in migration script at first
yuki-takei Jan 10, 2025
be81410
update IGrowiInfo interface to include serviceInstanceId and osInfo; …
yuki-takei Jan 10, 2025
708c45a
add changeset file
yuki-takei Jan 10, 2025
a02046f
refactor
yuki-takei Jan 15, 2025
8b4cd95
remove uuidv4 updating for app:serviceInstanceId in InstallerService
yuki-takei Jan 15, 2025
8b0af79
initialize service instance id after DB is initialized
yuki-takei Jan 15, 2025
ff689e6
refactor getSiteUrl()
yuki-takei Jan 15, 2025
47c7c3a
replace appService.getSiteUrl with growiInfoService.getSiteUrl
yuki-takei Jan 15, 2025
96af015
Add OIDC configuration keys and definitions
yuki-takei Jan 15, 2025
b97a6b6
implement getConfigLegacy
yuki-takei Jan 15, 2025
68055e1
Merge branch 'feat/opentelemetry' into imprv/generate-site-url-hashed…
yuki-takei Jan 17, 2025
5a455de
make passport service type safe
yuki-takei Jan 17, 2025
b32ee54
make codes type-safe
yuki-takei Jan 17, 2025
7386b48
add configs for mail settings
yuki-takei Jan 21, 2025
50de380
improve typings
yuki-takei Jan 21, 2025
2077c03
improve typings
yuki-takei Jan 21, 2025
f09bceb
Merge branch 'master' into imprv/generate-site-url-hashed-isolatedly
yuki-takei Jan 22, 2025
5026eac
improve typings
yuki-takei Jan 22, 2025
42c8038
Merge branch 'feat/opentelemetry' into imprv/generate-site-url-hashed…
yuki-takei Jan 22, 2025
85637a8
fix test
yuki-takei Jan 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/clever-impalas-dress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@growi/core': minor
---

Update GrowiInfo interface
18 changes: 9 additions & 9 deletions apps/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,15 @@
"@growi/remark-lsx": "workspace:^",
"@growi/slack": "workspace:^",
"@keycloak/keycloak-admin-client": "^18.0.0",
"@opentelemetry/api": "^1.8.0",
"@opentelemetry/auto-instrumentations-node": "^0.52.1",
"@opentelemetry/exporter-metrics-otlp-grpc": "^0.54.2",
"@opentelemetry/exporter-trace-otlp-grpc": "^0.54.2",
"@opentelemetry/resources": "^1.27.0",
"@opentelemetry/semantic-conventions": "^1.27.0",
"@opentelemetry/sdk-metrics": "^1.27.0",
"@opentelemetry/sdk-node": "^0.54.2",
"@opentelemetry/sdk-trace-node": "^1.27.0",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/auto-instrumentations-node": "^0.55.1",
"@opentelemetry/exporter-metrics-otlp-grpc": "^0.57.0",
"@opentelemetry/exporter-trace-otlp-grpc": "^0.57.0",
"@opentelemetry/resources": "^1.28.0",
"@opentelemetry/semantic-conventions": "^1.28.0",
"@opentelemetry/sdk-metrics": "^1.28.0",
"@opentelemetry/sdk-node": "^0.57.0",
"@opentelemetry/sdk-trace-node": "^1.28.0",
"@slack/web-api": "^6.2.4",
"@slack/webhook": "^6.0.0",
"JSONStream": "^1.3.5",
Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/features/opentelemetry/server/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from './start';
export * from './node-sdk';
Original file line number Diff line number Diff line change
@@ -1,35 +1,56 @@
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-grpc';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc';
import { Resource } from '@opentelemetry/resources';
import { Resource, type IResource } from '@opentelemetry/resources';
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
import type { NodeSDKConfiguration } from '@opentelemetry/sdk-node';
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION, SEMRESATTRS_SERVICE_INSTANCE_ID } from '@opentelemetry/semantic-conventions';

import { getGrowiVersion } from '~/utils/growi-version';

export const generateNodeSDKConfiguration = (instanceId: string, version: string): Partial<NodeSDKConfiguration> => {
return {
resource: new Resource({
type Configuration = Partial<NodeSDKConfiguration> & {
resource: IResource;
};

let resource: Resource;
let configuration: Configuration;

export const generateNodeSDKConfiguration = (serviceInstanceId?: string): Configuration => {
if (configuration == null) {
const version = getGrowiVersion();

resource = new Resource({
[ATTR_SERVICE_NAME]: 'growi',
[ATTR_SERVICE_VERSION]: version,
[SEMRESATTRS_SERVICE_INSTANCE_ID]: instanceId,
}),
traceExporter: new OTLPTraceExporter(),
metricReader: new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter(),
exportIntervalMillis: 10000,
}),
instrumentations: [getNodeAutoInstrumentations({
'@opentelemetry/instrumentation-bunyan': {
enabled: false,
},
// disable fs instrumentation since this generates very large amount of traces
// see: https://opentelemetry.io/docs/languages/js/libraries/#registration
'@opentelemetry/instrumentation-fs': {
enabled: false,
},
})],
};
});

configuration = {
resource,
traceExporter: new OTLPTraceExporter(),
metricReader: new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter(),
exportIntervalMillis: 10000,
}),
instrumentations: [getNodeAutoInstrumentations({
'@opentelemetry/instrumentation-bunyan': {
enabled: false,
},
// disable fs instrumentation since this generates very large amount of traces
// see: https://opentelemetry.io/docs/languages/js/libraries/#registration
'@opentelemetry/instrumentation-fs': {
enabled: false,
},
})],
};
}

if (serviceInstanceId != null) {
configuration.resource = resource.merge(new Resource({
[SEMRESATTRS_SERVICE_INSTANCE_ID]: serviceInstanceId,
}));
}

return configuration;
};

// public async shutdownInstrumentation(): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type { NodeSDK } from '@opentelemetry/sdk-node';
import { configManager } from '~/server/service/config-manager';
import loggerFactory from '~/utils/logger';


const logger = loggerFactory('growi:opentelemetry:server');


Expand Down Expand Up @@ -37,7 +36,7 @@ function overwriteSdkDisabled(): void {

}

export const startInstrumentation = async(version: string): Promise<void> => {
export const startInstrumentation = async(): Promise<void> => {
if (sdkInstance != null) {
logger.warn('OpenTelemetry instrumentation already started');
return;
Expand Down Expand Up @@ -69,14 +68,26 @@ For more information, see https://docs.growi.org/en/admin-guide/telemetry.html.
const { NodeSDK } = await import('@opentelemetry/sdk-node');
const { generateNodeSDKConfiguration } = await import('./node-sdk-configuration');

const serviceInstanceId = configManager.getConfig('otel:serviceInstanceId', ConfigSource.env)
?? 'generated-appSiteUrlHashed'; // TODO: generated appSiteUrlHashed

sdkInstance = new NodeSDK(generateNodeSDKConfiguration(serviceInstanceId, version));
sdkInstance = new NodeSDK(generateNodeSDKConfiguration());
sdkInstance.start();
}
};

export const initServiceInstanceId = async(): Promise<void> => {
const instrumentationEnabled = configManager.getConfig('otel:enabled', ConfigSource.env);

if (instrumentationEnabled) {
const { generateNodeSDKConfiguration } = await import('./node-sdk-configuration');

const serviceInstanceId = configManager.getConfig('otel:serviceInstanceId')
?? configManager.getConfig('app:serviceInstanceId');

// overwrite resource
const updatedResource = generateNodeSDKConfiguration(serviceInstanceId).resource;
(sdkInstance as any).resource = updatedResource;
}
};

// public async shutdownInstrumentation(): Promise<void> {
// await this.sdkInstance.shutdown();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ export type IGrowiAppAdditionalInfo = IGrowiAdditionalInfo & {

// legacy properties (extracted from additionalInfo for growi-questionnaire)
// see: https://gitlab.weseek.co.jp/tech/growi/growi-questionnaire
export type IGrowiAppInfoLegacy = Omit<IGrowiInfo<IGrowiAppAdditionalInfo>, 'additionalInfo'> & IGrowiAppAdditionalInfo;
export type IGrowiAppInfoLegacy = Omit<IGrowiInfo<IGrowiAppAdditionalInfo>, 'additionalInfo'>
& IGrowiAppAdditionalInfo
& {
appSiteUrlHashed: string,
};
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const growiAdditionalInfoSchema = new Schema<IGrowiAppAdditionalInfo>({
export const growiInfoSchema = new Schema<IGrowiInfo<IGrowiAppAdditionalInfo> & IGrowiAppAdditionalInfo>({
version: { type: String, required: true },
appSiteUrl: { type: String },
appSiteUrlHashed: { type: String, required: true },
serviceInstanceId: { type: String, required: true },
type: { type: String, required: true, enum: Object.values(GrowiServiceType) },
wikiType: { type: String, required: true, enum: Object.values(GrowiWikiType) },
osInfo: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type Crowi from '~/server/crowi';
import { accessTokenParser } from '~/server/middlewares/access-token-parser';
import type { ApiV3Response } from '~/server/routes/apiv3/interfaces/apiv3-response';
import { configManager } from '~/server/service/config-manager';
import { growiInfoService } from '~/server/service/growi-info';
import axios from '~/utils/axios';
import loggerFactory from '~/utils/logger';

Expand All @@ -17,7 +18,7 @@ import { StatusType } from '../../../interfaces/questionnaire-answer-status';
import ProactiveQuestionnaireAnswer from '../../models/proactive-questionnaire-answer';
import QuestionnaireAnswer from '../../models/questionnaire-answer';
import QuestionnaireAnswerStatus from '../../models/questionnaire-answer-status';
import { convertToLegacyFormat } from '../../util/convert-to-legacy-format';
import { convertToLegacyFormat, getSiteUrlHashed } from '../../util/convert-to-legacy-format';


const logger = loggerFactory('growi:routes:apiv3:questionnaire');
Expand Down Expand Up @@ -61,8 +62,8 @@ module.exports = (crowi: Crowi): Router => {
};

router.get('/orders', accessTokenParser, loginRequired, async(req: AuthorizedRequest, res: ApiV3Response) => {
const growiInfo = await crowi.questionnaireService!.getGrowiInfo();
const userInfo = crowi.questionnaireService!.getUserInfo(req.user ?? null, growiInfo.appSiteUrlHashed);
const growiInfo = await growiInfoService.getGrowiInfo(true);
const userInfo = crowi.questionnaireService.getUserInfo(req.user ?? null, getSiteUrlHashed(growiInfo.appSiteUrl));

try {
const questionnaireOrders = await crowi.questionnaireService!.getQuestionnaireOrdersToShow(userInfo, growiInfo, req.user?._id ?? null);
Expand All @@ -83,8 +84,9 @@ module.exports = (crowi: Crowi): Router => {
router.post('/proactive/answer', accessTokenParser, loginRequired, validators.proactiveAnswer, async(req: AuthorizedRequest, res: ApiV3Response) => {
const sendQuestionnaireAnswer = async() => {
const questionnaireServerOrigin = configManager.getConfig('app:questionnaireServerOrigin');
const growiInfo = await crowi.questionnaireService!.getGrowiInfo();
const userInfo = crowi.questionnaireService!.getUserInfo(req.user ?? null, growiInfo.appSiteUrlHashed);
const isAppSiteUrlHashed = configManager.getConfig('questionnaire:isAppSiteUrlHashed');
const growiInfo = await growiInfoService.getGrowiInfo(true);
const userInfo = crowi.questionnaireService.getUserInfo(req.user ?? null, getSiteUrlHashed(growiInfo.appSiteUrl));

const proactiveQuestionnaireAnswer: IProactiveQuestionnaireAnswer = {
satisfaction: req.body.satisfaction,
Expand All @@ -97,7 +99,7 @@ module.exports = (crowi: Crowi): Router => {
answeredAt: new Date(),
};

const proactiveQuestionnaireAnswerLegacy = convertToLegacyFormat(proactiveQuestionnaireAnswer);
const proactiveQuestionnaireAnswerLegacy = convertToLegacyFormat(proactiveQuestionnaireAnswer, isAppSiteUrlHashed);

try {
await axios.post(`${questionnaireServerOrigin}/questionnaire-answer/proactive`, proactiveQuestionnaireAnswerLegacy);
Expand Down Expand Up @@ -131,8 +133,9 @@ module.exports = (crowi: Crowi): Router => {
router.put('/answer', accessTokenParser, loginRequired, validators.answer, async(req: AuthorizedRequest, res: ApiV3Response) => {
const sendQuestionnaireAnswer = async(user: IUserHasId, answers: IAnswer[]) => {
const questionnaireServerOrigin = crowi.configManager.getConfig('app:questionnaireServerOrigin');
const growiInfo = await crowi.questionnaireService!.getGrowiInfo();
const userInfo = crowi.questionnaireService!.getUserInfo(user, growiInfo.appSiteUrlHashed);
const isAppSiteUrlHashed = configManager.getConfig('questionnaire:isAppSiteUrlHashed');
const growiInfo = await growiInfoService.getGrowiInfo(true);
const userInfo = crowi.questionnaireService.getUserInfo(user, getSiteUrlHashed(growiInfo.appSiteUrl));

const questionnaireAnswer: IQuestionnaireAnswer = {
growiInfo,
Expand All @@ -142,7 +145,7 @@ module.exports = (crowi: Crowi): Router => {
questionnaireOrder: req.body.questionnaireOrderId,
};

const questionnaireAnswerLegacy = convertToLegacyFormat(questionnaireAnswer);
const questionnaireAnswerLegacy = convertToLegacyFormat(questionnaireAnswer, isAppSiteUrlHashed);

try {
await axios.post(`${questionnaireServerOrigin}/questionnaire-answer`, questionnaireAnswerLegacy);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import axiosRetry from 'axios-retry';

import { configManager } from '~/server/service/config-manager';
import loggerFactory from '~/utils/logger';
import { getRandomIntInRange } from '~/utils/rand';

Expand Down Expand Up @@ -54,7 +55,8 @@ class QuestionnaireCronService {
}

async executeJob(): Promise<void> {
const questionnaireServerOrigin = this.crowi.configManager.getConfig('app:questionnaireServerOrigin');
const questionnaireServerOrigin = configManager.getConfig('app:questionnaireServerOrigin');
const isAppSiteUrlHashed = configManager.getConfig('questionnaire:isAppSiteUrlHashed');

const fetchQuestionnaireOrders = async(): Promise<IQuestionnaireOrder[]> => {
const response = await axios.get(`${questionnaireServerOrigin}/questionnaire-order/index`);
Expand Down Expand Up @@ -84,14 +86,14 @@ class QuestionnaireCronService {

axios.post(`${questionnaireServerOrigin}/questionnaire-answer/batch`, {
// convert to legacy format
questionnaireAnswers: questionnaireAnswers.map(answer => convertToLegacyFormat(answer)),
questionnaireAnswers: questionnaireAnswers.map(answer => convertToLegacyFormat(answer, isAppSiteUrlHashed)),
})
.then(async() => {
await QuestionnaireAnswer.deleteMany();
});
axios.post(`${questionnaireServerOrigin}/questionnaire-answer/proactive/batch`, {
// convert to legacy format
proactiveQuestionnaireAnswers: proactiveQuestionnaireAnswers.map(answer => convertToLegacyFormat(answer)),
proactiveQuestionnaireAnswers: proactiveQuestionnaireAnswers.map(answer => convertToLegacyFormat(answer, isAppSiteUrlHashed)),
})
.then(async() => {
await ProactiveQuestionnaireAnswer.deleteMany();
Expand Down
Loading
Loading