Skip to content

Commit

Permalink
feat: add dynamic config pooler
Browse files Browse the repository at this point in the history
  • Loading branch information
jhoncool committed Jul 25, 2023
1 parent 68a9120 commit b3fab1f
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 3 deletions.
3 changes: 1 addition & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export {NodeKit} from './nodekit';
export {AppContext} from './lib/context';
export {AppConfig} from './types';
export {AppContextParams} from './types';
export {AppConfig, AppContextParams, AppDynamicConfig} from './types';
export {AppError} from './lib/app-error';
6 changes: 5 additions & 1 deletion src/lib/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import pino from 'pino';
import {JaegerTracer} from 'jaeger-client';
import {Span, Tags, SpanContext, FORMAT_HTTP_HEADERS} from 'opentracing';
import {NodeKit} from '../nodekit';
import {AppConfig, AppContextParams, Dict} from '../types';
import {AppConfig, AppContextParams, Dict, AppDynamicConfig} from '../types';
import {AppError} from './app-error';
import {extractErrorInfo} from './error-parser';
import {IncomingHttpHeaders} from 'http';
Expand All @@ -17,6 +17,7 @@ interface ContextInitialParams {
stats: AppTelemetrySendStats;
parentSpanContext?: SpanContext;
utils: NodeKit['utils'];
dynamicConfig?: AppDynamicConfig;
loggerPostfix?: string;
tags?: Dict;
}
Expand All @@ -42,6 +43,7 @@ export class AppContext {
parentContext?: AppContext;
utils: NodeKit['utils'];
stats: AppTelemetrySendStats;
dynamicConfig: AppDynamicConfig;

protected appParams: AppContextParams;
protected name: string;
Expand All @@ -62,6 +64,7 @@ export class AppContext {
this.logger = params.parentContext.logger;
this.tracer = params.parentContext.tracer;
this.utils = params.parentContext.utils;
this.dynamicConfig = params.parentContext.dynamicConfig;
this.appParams = Object.assign({}, params.parentContext?.appParams);
this.loggerPrefix = `${params.parentContext.loggerPrefix} [${this.name}]`.trim();
this.loggerPostfix = params.loggerPostfix || params.parentContext.loggerPostfix;
Expand All @@ -77,6 +80,7 @@ export class AppContext {
this.logger = params.logger;
this.tracer = params.tracer;
this.utils = params.utils;
this.dynamicConfig = {};
this.loggerPrefix = '';
this.loggerPostfix = params.loggerPostfix || '';
this.stats = params.stats;
Expand Down
68 changes: 68 additions & 0 deletions src/lib/dynamic-config-poller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import axios, {AxiosError} from 'axios';
import type {AppContext} from './context';

const DYNAMIC_CONFIG_POLL_INTERVAL = 30000;

export interface DynamicConfigSetup {
url: string;
interval?: number;
}

export class DynamicConfigPoller {
ctx: AppContext;

private namespace: string;
private dynamicConfigSetup: DynamicConfigSetup;

constructor(ctx: AppContext, namespace: string, dynamicConfigSetup: DynamicConfigSetup) {
this.ctx = ctx;
this.namespace = namespace;
this.dynamicConfigSetup = dynamicConfigSetup;
}

startPolling = () => {
const {dynamicConfigSetup, namespace} = this;

if (process.env.APP_DEBUG_DYNAMIC_CONFIG) {
this.ctx.log('Dynamic config: fetching started', {
namespace,
});
}

axios
.get(`${dynamicConfigSetup.url}?cacheInvalidation=${Date.now()}`)
.then(this.onSuccess, this.onError);
};

private getPollTimeout() {
return this.dynamicConfigSetup.interval || DYNAMIC_CONFIG_POLL_INTERVAL;
}

private onSuccess = (response: {data: Record<string, boolean>}) => {
const {namespace} = this;

if (process.env.APP_DEBUG_DYNAMIC_CONFIG) {
this.ctx.log('Dynamic config: fetch complete', {
oldDynamicConfig: (this.ctx.dynamicConfig as Record<string, any>)[namespace],
fetchedDynamicConfig: response.data,
namespace,
});
}

(this.ctx.dynamicConfig as Record<string, any>)[namespace] = response.data;

setTimeout(this.startPolling, this.getPollTimeout());
};

private onError = (error: AxiosError) => {
const timeout = this.getPollTimeout();
const {namespace} = this;

this.ctx.logError('Dynamic config: fetch failed', error, {
timeout,
namespace,
});

setTimeout(this.startPolling, timeout);
};
}
5 changes: 5 additions & 0 deletions src/nodekit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
SensitiveKeysRedacter,
} from './lib/utils/redact-sensitive-keys';
import {prepareClickhouseClient} from './lib/telemetry/clickhouse';
import {DynamicConfigSetup, DynamicConfigPoller} from './lib/dynamic-config-poller';

interface InitOptions {
disableDotEnv?: boolean;
Expand Down Expand Up @@ -104,6 +105,10 @@ export class NodeKit {
this.shutdownHandlers.push(handler);
}

setupDynamicConfig(namespace: string, dynamicConfigSetup: DynamicConfigSetup) {
new DynamicConfigPoller(this.ctx, namespace, dynamicConfigSetup).startPolling();
}

private setupShutdownSignals() {
const signals = ['SIGTERM', 'SIGINT'] as const;

Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export interface AppConfig {

export interface AppContextParams {}

export interface AppDynamicConfig {}

export type Dict = {[key: string]: unknown};

export interface ShutdownHandler {
Expand Down

0 comments on commit b3fab1f

Please sign in to comment.