-
Notifications
You must be signed in to change notification settings - Fork 4.3k
feat(api-service): update logger level runtime #9902
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
base: next
Are you sure you want to change the base?
Changes from all commits
d816d46
fc547be
aebe9ad
ebb56be
022dda8
8496b30
cd53648
eb44567
456c1d1
da59dfa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export * from './log-level.service'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| import { Injectable, Logger, OnModuleDestroy, OnModuleInit } from '@nestjs/common'; | ||
| import { FeatureFlagsKeysEnum } from '@novu/shared'; | ||
| import { PinoLogger } from 'nestjs-pino'; | ||
| import { getLogLevel, loggingLevelArr } from '../../logging'; | ||
| import { FeatureFlagsService } from '../feature-flags'; | ||
|
|
||
| const LOG_CONTEXT = 'LogLevelService'; | ||
| const DEFAULT_POLLING_INTERVAL_MS = 60_000; // one minute | ||
|
|
||
| @Injectable() | ||
| export class LogLevelService implements OnModuleInit, OnModuleDestroy { | ||
| private pollingInterval: NodeJS.Timeout | null = null; | ||
| private currentLogLevel: string; | ||
| private readonly pollingIntervalMs: number; | ||
|
|
||
| constructor(private featureFlagsService: FeatureFlagsService) { | ||
| this.pollingIntervalMs = Number(process.env.LOG_LEVEL_POLLING_INTERVAL_MS) || DEFAULT_POLLING_INTERVAL_MS; | ||
| this.currentLogLevel = getLogLevel(); | ||
| } | ||
|
|
||
| async onModuleInit(): Promise<void> { | ||
| await this.updateLogLevel(); | ||
|
|
||
| this.pollingInterval = setInterval(async () => { | ||
| try { | ||
| await this.updateLogLevel(); | ||
| } catch (error) { | ||
| Logger.error(`Failed to update log level: ${(error as Error).message}`, (error as Error).stack, LOG_CONTEXT); | ||
| } | ||
| }, this.pollingIntervalMs); | ||
|
|
||
| Logger.log(`Log level polling started with interval of ${this.pollingIntervalMs}ms`, LOG_CONTEXT); | ||
| } | ||
|
|
||
| onModuleDestroy(): void { | ||
| if (this.pollingInterval) { | ||
| clearInterval(this.pollingInterval); | ||
| this.pollingInterval = null; | ||
| Logger.log('Log level polling stopped', LOG_CONTEXT); | ||
| } | ||
| } | ||
|
|
||
| private async updateLogLevel(): Promise<void> { | ||
| const logLevelFromFlag = await this.getLogLevelFromFeatureFlag(); | ||
|
|
||
| const newLogLevel = logLevelFromFlag || this.getFallbackLogLevel(); | ||
|
|
||
| if (!this.isValidLogLevel(newLogLevel)) { | ||
| Logger.warn( | ||
| `Invalid log level "${newLogLevel}". Valid levels: ${loggingLevelArr.join(', ')}. Keeping current level: ${this.currentLogLevel}`, | ||
| LOG_CONTEXT | ||
| ); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| if (newLogLevel !== this.currentLogLevel) { | ||
| this.setLogLevel(newLogLevel); | ||
| Logger.log(`Log level changed from "${this.currentLogLevel}" to "${newLogLevel}"`, LOG_CONTEXT); | ||
| this.currentLogLevel = newLogLevel; | ||
| } | ||
| } | ||
|
|
||
| private async getLogLevelFromFeatureFlag(): Promise<string | undefined> { | ||
| try { | ||
| const flagValue = await this.featureFlagsService.getFlag<string | undefined>({ | ||
| key: FeatureFlagsKeysEnum.LOG_LEVEL_STR, | ||
| defaultValue: undefined, | ||
| user: { _id: 'system' }, | ||
| }); | ||
|
|
||
| if (flagValue && flagValue !== 'undefined') { | ||
| return flagValue; | ||
| } | ||
|
|
||
| return undefined; | ||
| } catch (error) { | ||
| Logger.warn(`Failed to get log level from feature flag: ${(error as Error).message}`, LOG_CONTEXT); | ||
|
|
||
| return undefined; | ||
| } | ||
| } | ||
|
|
||
| private getFallbackLogLevel(): string { | ||
| return process.env.LOG_LEVEL || process.env.LOGGING_LEVEL || 'info'; | ||
| } | ||
|
|
||
| private isValidLogLevel(level: string): boolean { | ||
| return loggingLevelArr.includes(level); | ||
| } | ||
|
|
||
| private setLogLevel(level: string): void { | ||
| if (PinoLogger.root) { | ||
| PinoLogger.root.level = level; | ||
| } | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -86,9 +86,12 @@ export enum FeatureFlagsKeysEnum { | |||||||||
| IS_BILLING_USAGE_CLICKHOUSE_ENABLED = 'IS_BILLING_USAGE_CLICKHOUSE_ENABLED', | ||||||||||
| IS_BILLING_USAGE_CLICKHOUSE_SHADOW_ENABLED = 'IS_BILLING_USAGE_CLICKHOUSE_SHADOW_ENABLED', | ||||||||||
| IS_BILLING_USAGE_DETAILED_DIAGNOSTICS_ENABLED = 'IS_BILLING_USAGE_DETAILED_DIAGNOSTICS_ENABLED', | ||||||||||
| IS_ANALYTICS_PAGE_ENABLED = 'IS_ANALYTICS_PAGE_ENABLED', | ||||||||||
| IS_LEGACY_SELECTOR_BUTTON_VISIBLE = 'IS_LEGACY_SELECTOR_BUTTON_VISIBLE', | ||||||||||
|
Comment on lines
+89
to
+90
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cat -n packages/shared/src/types/feature-flags.ts | head -120Repository: novuhq/novu Length of output: 7466 🏁 Script executed: rg -n "testFlagEnumValidity" --type=ts -A5 -B5Repository: novuhq/novu Length of output: 3344 🏁 Script executed: rg -n "BooleanFlagKey|NumericFlagKey" --type=ts -A2 -B2Repository: novuhq/novu Length of output: 841 🏁 Script executed: rg -n "testFlagEnumValidity.*FeatureFlagsKeysEnum" --type=tsRepository: novuhq/novu Length of output: 37 🏁 Script executed: rg -A5 "export enum FeatureFlagsKeysEnum" packages/shared/src/types/feature-flags.tsRepository: novuhq/novu Length of output: 412
The Suggested fix- IS_LEGACY_SELECTOR_BUTTON_VISIBLE = 'IS_LEGACY_SELECTOR_BUTTON_VISIBLE',
+ IS_LEGACY_SELECTOR_BUTTON_ENABLED = 'IS_LEGACY_SELECTOR_BUTTON_ENABLED',📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
|
|
||||||||||
| // String flags | ||||||||||
| CF_SCHEDULER_MODE = 'CF_SCHEDULER_MODE', // Values: "off" | "shadow" | "live" | "complete" | ||||||||||
| CF_SCHEDULER_MODE_STR = 'CF_SCHEDULER_MODE_STR', // Values: "off" | "shadow" | "live" | "complete" | ||||||||||
|
djabarovgeorge marked this conversation as resolved.
|
||||||||||
| LOG_LEVEL_STR = 'LOG_LEVEL_STR', // Values: "trace" | "debug" | "info" | "warn" | "error" | "fatal" | "none" | ||||||||||
|
|
||||||||||
| // Numeric flags | ||||||||||
| MAX_WORKFLOW_LIMIT_NUMBER = 'MAX_WORKFLOW_LIMIT_NUMBER', | ||||||||||
|
|
@@ -99,8 +102,6 @@ export enum FeatureFlagsKeysEnum { | |||||||||
| LOG_EXPIRATION_DAYS_NUMBER = 'LOG_EXPIRATION_DAYS_NUMBER', | ||||||||||
| MAX_DATE_ANALYTICS_ENABLED_NUMBER = 'MAX_DATE_ANALYTICS_ENABLED_NUMBER', | ||||||||||
| MAX_ENVIRONMENT_COUNT = 'MAX_ENVIRONMENT_COUNT', | ||||||||||
| IS_ANALYTICS_PAGE_ENABLED = 'IS_ANALYTICS_PAGE_ENABLED', | ||||||||||
| IS_LEGACY_SELECTOR_BUTTON_VISIBLE = 'IS_LEGACY_SELECTOR_BUTTON_VISIBLE', | ||||||||||
| } | ||||||||||
|
|
||||||||||
| export enum CloudflareSchedulerMode { | ||||||||||
|
|
||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: novuhq/novu
Length of output: 1239
🏁 Script executed:
Repository: novuhq/novu
Length of output: 1380
🏁 Script executed:
Repository: novuhq/novu
Length of output: 37
🏁 Script executed:
Repository: novuhq/novu
Length of output: 2226
🏁 Script executed:
Repository: novuhq/novu
Length of output: 3772
MAX_ENVIRONMENT_COUNTwon't get a numeric validator.The filter
key.endsWith('_NUMBER')will missMAX_ENVIRONMENT_COUNTinFeatureFlagsKeysEnum. It's defined as a numeric flag but doesn't follow the_NUMBERsuffix convention, so it won't receive a numeric validator.🤖 Prompt for AI Agents