Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
54 changes: 52 additions & 2 deletions packages/collector/src/announceCycle/unannounced.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ const {
secrets,
tracing,
util: { ensureNestedObjectExists },
coreConfig: { configNormalizers }
coreConfig: { configNormalizers, configValidators }
} = require('@instana/core');

const { validateStackTraceMode, validateStackTraceLength } = configValidators.stackTraceValidation;

const { constants: tracingConstants } = tracing;

const agentConnection = require('../agentConnection');
Expand All @@ -24,7 +27,6 @@ let pidStore;
const initialRetryDelay = 10 * 1000; // 10 seconds
const backoffFactor = 1.5;
const maxRetryDelay = 60 * 1000; // one minute

/**
* @typedef {Object} AgentAnnounceResponse
* @property {SecretsConfig} [secrets]
Expand All @@ -46,6 +48,13 @@ const maxRetryDelay = 60 * 1000; // one minute
* @property {import('@instana/core/src/config/types').IgnoreEndpoints} [ignore-endpoints]
* @property {boolean} [span-batching-enabled]
* @property {import('@instana/core/src/config/types').Disable} [disable]
* @property {StackTraceConfig} [global]
*/

/**
* @typedef {Object} StackTraceConfig
* @property {string} [stack-trace] - Stack trace mode ('error'|'all'|'none')
* @property {number} [stack-trace-length] - Maximum number of stack trace frames to capture
*/

/**
Expand Down Expand Up @@ -121,6 +130,7 @@ function applyAgentConfiguration(agentResponse) {
applyKafkaTracingConfiguration(agentResponse);
applySpanBatchingConfiguration(agentResponse);
applyIgnoreEndpointsConfiguration(agentResponse);
applyStackTraceConfiguration(agentResponse);
applyDisableConfiguration(agentResponse);
}

Expand Down Expand Up @@ -240,6 +250,46 @@ function applyIgnoreEndpointsConfiguration(agentResponse) {
agentOpts.config.tracing.ignoreEndpoints = configNormalizers.ignoreEndpoints.normalizeConfig(ignoreEndpointsConfig);
}

/**
* Apply global stack trace configuration from the agent response.
*
* @param {AgentAnnounceResponse} agentResponse
*/
function applyStackTraceConfiguration(agentResponse) {
const globalConfig = agentResponse?.tracing?.global;
if (!globalConfig) return;

ensureNestedObjectExists(agentOpts.config, ['tracing', 'global']);

if (globalConfig['stack-trace'] !== undefined) {
const stackTraceModeValidation = validateStackTraceMode(globalConfig['stack-trace']);
if (stackTraceModeValidation.isValid) {
const normalizedStackTrace = configNormalizers.stackTrace.normalizeStackTraceModeFromAgent(
globalConfig['stack-trace']
);
if (normalizedStackTrace != null) {
agentOpts.config.tracing.stackTrace = normalizedStackTrace;
}
} else {
logger.warn(`Invalid stack-trace value from agent: ${stackTraceModeValidation.error}`);
}
}

if (globalConfig['stack-trace-length'] !== undefined) {
const stackTraceLengthValidation = validateStackTraceLength(globalConfig['stack-trace-length']);
if (stackTraceLengthValidation.isValid) {
const normalizedStackTraceLength = configNormalizers.stackTrace.normalizeStackTraceLengthFromAgent(
globalConfig['stack-trace-length']
);
if (normalizedStackTraceLength != null) {
agentOpts.config.tracing.stackTraceLength = normalizedStackTraceLength;
}
} else {
logger.warn(`Invalid stack-trace-length value from agent: ${stackTraceLengthValidation.error}`);
}
}
}

/**
* The incoming agent configuration include `disable` object that include
* which instrumentation/categories should be disabled. For example: { logging: true, console: false }
Expand Down
4 changes: 4 additions & 0 deletions packages/collector/src/types/collector.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ export interface AgentConfig {
ignoreEndpoints?: IgnoreEndpoints;
disable?: Disable;
[key: string]: any;
global?: {
stackTrace?: string;
stackTraceLength?: number;
};
};
[key: string]: any;
}
Expand Down
253 changes: 253 additions & 0 deletions packages/collector/test/announceCycle/unannounced_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,259 @@ describe('unannounced state', () => {
});
});

describe('applyStackTraceConfiguration', () => {
it('should apply stack trace mode from agent response', done => {
prepareAnnounceResponse({
tracing: {
global: {
'stack-trace': 'all'
}
}
});
unannouncedState.enter({
transitionTo: () => {
expect(agentOptsStub.config).to.deep.equal({
tracing: {
stackTrace: 'all',
global: {}
}
});
done();
}
});
});

it('should apply stack trace length from agent response', done => {
prepareAnnounceResponse({
tracing: {
global: {
'stack-trace-length': 15
}
}
});
unannouncedState.enter({
transitionTo: () => {
expect(agentOptsStub.config).to.deep.equal({
tracing: {
stackTraceLength: 15,
global: {}
}
});
done();
}
});
});

it('should apply both stack trace mode and length from agent response (mixed case)', done => {
prepareAnnounceResponse({
tracing: {
global: {
'stack-trace': 'eRrOr',
'stack-trace-length': 25
}
}
});
unannouncedState.enter({
transitionTo: () => {
expect(agentOptsStub.config).to.deep.equal({
tracing: {
stackTrace: 'error',
stackTraceLength: 25,
global: {}
}
});
done();
}
});
});

it('should normalize stack trace mode to lowercase', done => {
prepareAnnounceResponse({
tracing: {
global: {
'stack-trace': 'ERROR'
}
}
});
unannouncedState.enter({
transitionTo: () => {
expect(agentOptsStub.config).to.deep.equal({
tracing: {
stackTrace: 'error',
global: {}
}
});
done();
}
});
});

it('should handle none stack trace mode', done => {
prepareAnnounceResponse({
tracing: {
global: {
'stack-trace': 'none'
}
}
});
unannouncedState.enter({
transitionTo: () => {
expect(agentOptsStub.config).to.deep.equal({
tracing: {
stackTrace: 'none',
global: {}
}
});
done();
}
});
});

it('should not apply stack trace config when tracing.global is missing', done => {
prepareAnnounceResponse({
tracing: {}
});
unannouncedState.enter({
transitionTo: () => {
expect(agentOptsStub.config).to.deep.equal({});
done();
}
});
});

it('should not apply stack trace config when tracing is missing', done => {
prepareAnnounceResponse({});
unannouncedState.enter({
transitionTo: () => {
expect(agentOptsStub.config).to.deep.equal({});
done();
}
});
});

it('should handle invalid stack trace mode gracefully', done => {
prepareAnnounceResponse({
tracing: {
global: {
'stack-trace': 'invalid-mode'
}
}
});
unannouncedState.enter({
transitionTo: () => {
expect(agentOptsStub.config).to.deep.equal({
tracing: {
global: {}
}
});
done();
}
});
});

it('should handle invalid stack trace length gracefully', done => {
prepareAnnounceResponse({
tracing: {
global: {
'stack-trace-length': 'not-a-number'
}
}
});
unannouncedState.enter({
transitionTo: () => {
expect(agentOptsStub.config).to.deep.equal({
tracing: {
global: {}
}
});
done();
}
});
});

it('should handle negative stack trace length gracefully', done => {
prepareAnnounceResponse({
tracing: {
global: {
'stack-trace-length': -5
}
}
});
unannouncedState.enter({
transitionTo: () => {
expect(agentOptsStub.config).to.deep.equal({
tracing: {
global: {},
stackTraceLength: 5
}
});
done();
}
});
});

it('should handle zero stack trace length', done => {
prepareAnnounceResponse({
tracing: {
global: {
'stack-trace-length': 0
}
}
});
unannouncedState.enter({
transitionTo: () => {
expect(agentOptsStub.config).to.deep.equal({
tracing: {
stackTraceLength: 0,
global: {}
}
});
done();
}
});
});

it('should apply valid config and ignore invalid config', done => {
prepareAnnounceResponse({
tracing: {
global: {
'stack-trace': 'all',
'stack-trace-length': 'invalid'
}
}
});
unannouncedState.enter({
transitionTo: () => {
expect(agentOptsStub.config).to.deep.equal({
tracing: {
stackTrace: 'all',
global: {}
}
});
done();
}
});
});

it('should handle empty global object', done => {
prepareAnnounceResponse({
tracing: {
global: {}
}
});
unannouncedState.enter({
transitionTo: () => {
expect(agentOptsStub.config).to.deep.equal({
tracing: {
global: {}
}
});
done();
}
});
});
});

function prepareAnnounceResponse(announceResponse) {
agentConnectionStub.announceNodeCollector.callsArgWithAsync(0, null, JSON.stringify(announceResponse));
}
Expand Down
14 changes: 13 additions & 1 deletion packages/collector/test/apps/agentStub.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const kafkaTraceCorrelation = process.env.KAFKA_TRACE_CORRELATION
: null;
const ignoreEndpoints = process.env.IGNORE_ENDPOINTS && JSON.parse(process.env.IGNORE_ENDPOINTS);
const disable = process.env.AGENT_DISABLE_TRACING && JSON.parse(process.env.AGENT_DISABLE_TRACING);
const stackTraceConfig = process.env.STACK_TRACE_CONFIG && JSON.parse(process.env.STACK_TRACE_CONFIG);

const uuids = {};
const agentLogs = [];
Expand Down Expand Up @@ -118,7 +119,14 @@ app.put('/com.instana.plugin.nodejs.discovery', (req, res) => {
}
};

if (kafkaTraceCorrelation != null || extraHeaders.length > 0 || enableSpanBatching || ignoreEndpoints || disable) {
if (
kafkaTraceCorrelation != null ||
extraHeaders.length > 0 ||
enableSpanBatching ||
ignoreEndpoints ||
disable ||
stackTraceConfig
) {
response.tracing = {};

if (extraHeaders.length > 0) {
Expand All @@ -141,6 +149,10 @@ app.put('/com.instana.plugin.nodejs.discovery', (req, res) => {
if (disable) {
response.tracing.disable = disable;
}
if (stackTraceConfig) {
response.tracing.global = response.tracing.global || {};
deepMerge(response.tracing.global, stackTraceConfig);
}
}

res.send(response);
Expand Down
Loading