Skip to content

Commit

Permalink
Merge pull request #9542 from weseek/imprv/generate-site-url-hashed-i…
Browse files Browse the repository at this point in the history
…solatedly

imprv: Set the service instance id after DB is initialized
  • Loading branch information
yuki-takei authored Jan 22, 2025
2 parents 6d1e2e2 + 85637a8 commit d0bed9b
Show file tree
Hide file tree
Showing 65 changed files with 1,200 additions and 771 deletions.
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

0 comments on commit d0bed9b

Please sign in to comment.