node-sdk.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. import { ConfigSource } from '@growi/core/dist/interfaces';
  2. import type { NodeSDK } from '@opentelemetry/sdk-node';
  3. import { configManager } from '~/server/service/config-manager';
  4. import loggerFactory from '~/utils/logger';
  5. import { setupCustomMetrics } from './custom-metrics';
  6. import { setResource } from './node-sdk-resource';
  7. const logger = loggerFactory('growi:opentelemetry:server');
  8. let sdkInstance: NodeSDK | undefined;
  9. /**
  10. * Overwrite "OTEL_SDK_DISABLED" env var before sdk.start() is invoked if needed.
  11. * Since otel library sees it.
  12. */
  13. function overwriteSdkDisabled(): void {
  14. const instrumentationEnabled = configManager.getConfig('otel:enabled', ConfigSource.env);
  15. if (instrumentationEnabled && (
  16. process.env.OTEL_SDK_DISABLED === 'true'
  17. || process.env.OTEL_SDK_DISABLED === '1'
  18. )) {
  19. logger.warn("OTEL_SDK_DISABLED overwritten with 'false' since GROWI's 'otel:enabled' config is true.");
  20. process.env.OTEL_SDK_DISABLED = 'false';
  21. return;
  22. }
  23. if (!instrumentationEnabled && (
  24. process.env.OTEL_SDK_DISABLED === 'false'
  25. || process.env.OTEL_SDK_DISABLED === '0'
  26. )) {
  27. logger.warn("OTEL_SDK_DISABLED is overwritten with 'true' since GROWI's 'otel:enabled' config is false.");
  28. process.env.OTEL_SDK_DISABLED = 'true';
  29. return;
  30. }
  31. }
  32. export const initInstrumentation = async(): Promise<void> => {
  33. if (sdkInstance != null) {
  34. logger.warn('OpenTelemetry instrumentation already started');
  35. return;
  36. }
  37. // load configs from env
  38. await configManager.loadConfigs({ source: ConfigSource.env });
  39. overwriteSdkDisabled();
  40. const instrumentationEnabled = configManager.getConfig('otel:enabled', ConfigSource.env);
  41. if (instrumentationEnabled) {
  42. logger.info(`GROWI now collects anonymous telemetry.
  43. This data is used to help improve GROWI, but you can opt-out at any time.
  44. For more information, see https://docs.growi.org/en/admin-guide/admin-cookbook/telemetry.html.
  45. `);
  46. // initialize global logger for development
  47. const isDev = process.env.NODE_ENV === 'development';
  48. if (isDev) {
  49. const { initLogger } = await import('./logger');
  50. initLogger();
  51. }
  52. // instanciate NodeSDK
  53. const { NodeSDK } = await import('@opentelemetry/sdk-node');
  54. const { generateNodeSDKConfiguration } = await import('./node-sdk-configuration');
  55. // get resource from configuration
  56. const enableAnonymization = configManager.getConfig('otel:anonymizeInBestEffort', ConfigSource.env);
  57. const sdkConfig = generateNodeSDKConfiguration({ enableAnonymization });
  58. setupCustomMetrics();
  59. sdkInstance = new NodeSDK(sdkConfig);
  60. }
  61. };
  62. export const setupAdditionalResourceAttributes = async(): Promise<void> => {
  63. const instrumentationEnabled = configManager.getConfig('otel:enabled', ConfigSource.env);
  64. if (instrumentationEnabled) {
  65. if (sdkInstance == null) {
  66. throw new Error('OpenTelemetry instrumentation is not initialized');
  67. }
  68. const { generateAdditionalResourceAttributes } = await import('./node-sdk-configuration');
  69. // get resource from configuration
  70. const enableAnonymization = configManager.getConfig('otel:anonymizeInBestEffort', ConfigSource.env);
  71. // generate additional resource attributes
  72. const updatedResource = await generateAdditionalResourceAttributes({ enableAnonymization });
  73. // set resource to sdk instance
  74. setResource(sdkInstance, updatedResource);
  75. }
  76. };
  77. export const startOpenTelemetry = (): void => {
  78. const instrumentationEnabled = configManager.getConfig('otel:enabled', ConfigSource.env);
  79. if (instrumentationEnabled && sdkInstance != null) {
  80. if (sdkInstance == null) {
  81. throw new Error('OpenTelemetry instrumentation is not initialized');
  82. }
  83. sdkInstance.start();
  84. }
  85. };
  86. // For testing purposes only
  87. export const __testing__ = {
  88. getSdkInstance: (): NodeSDK | undefined => sdkInstance,
  89. reset: (): void => {
  90. sdkInstance = undefined;
  91. },
  92. };