diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5283f97..c8261ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,7 @@ on: - "**" jobs: - build: + test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 diff --git a/package-lock.json b/package-lock.json index e5a8a1a..605f464 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@coralogix/opentelemetry", - "version": "0.1.1", + "version": "0.1.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@coralogix/opentelemetry", - "version": "0.1.1", + "version": "0.1.2", "dependencies": { "@opentelemetry/api": "^1.7.0", "@opentelemetry/sdk-trace-base": "^1.18.1" diff --git a/package.json b/package.json index 1badc9d..845b7b8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@coralogix/opentelemetry", - "version": "0.1.1", + "version": "0.1.2", "main": "dist/index.js", "module": "dist/index.mjs", "types": "dist/index.d.ts", diff --git a/src/trace/common/coralogix-attributes.ts b/src/trace/common/coralogix-attributes.ts index 7842115..d46610a 100644 --- a/src/trace/common/coralogix-attributes.ts +++ b/src/trace/common/coralogix-attributes.ts @@ -1,4 +1,5 @@ export const CoralogixAttributes = { TRANSACTION_IDENTIFIER: 'cgx.transaction', DISTRIBUTED_TRANSACTION_IDENTIFIER: 'cgx.transaction.distributed', + TRANSACTION_ROOT: 'cgx.transaction.root', } \ No newline at end of file diff --git a/src/trace/samplers/coralogix-transaction-sampler.ts b/src/trace/samplers/coralogix-transaction-sampler.ts index c96c1fc..ecbf870 100644 --- a/src/trace/samplers/coralogix-transaction-sampler.ts +++ b/src/trace/samplers/coralogix-transaction-sampler.ts @@ -26,9 +26,12 @@ export class CoralogixTransactionSampler implements Sampler { // if distributed transaction exists, use it, if not this is the first span and thus the root of the distributed transaction const distributedTransaction = spanContext?.traceState?.get(CoralogixTraceState.DISTRIBUTED_TRANSACTION_IDENTIFIER) ?? spanName; + const existingTransaction = spanContext?.traceState?.get(CoralogixTraceState.TRANSACTION_IDENTIFIER); + // if span is remote, then start a new transaction, else try to use existing transaction - const transaction = spanContext?.isRemote ? spanName : - (spanContext?.traceState?.get(CoralogixTraceState.TRANSACTION_IDENTIFIER) ?? spanName) + const startsTransaction = existingTransaction === undefined || spanContext?.isRemote; + + const transaction = startsTransaction ? spanName : existingTransaction; let {attributes: resultAttributes, traceState} = result; const {decision} = result; @@ -40,7 +43,8 @@ export class CoralogixTransactionSampler implements Sampler { resultAttributes = { ...(resultAttributes ?? {}), [CoralogixAttributes.TRANSACTION_IDENTIFIER]: transaction, - [CoralogixAttributes.DISTRIBUTED_TRANSACTION_IDENTIFIER]: distributedTransaction + [CoralogixAttributes.DISTRIBUTED_TRANSACTION_IDENTIFIER]: distributedTransaction, + [CoralogixAttributes.TRANSACTION_ROOT]: startsTransaction ?? undefined } return { diff --git a/test/trace/samplers/coralogix-transaction-sampler.spec.ts b/test/trace/samplers/coralogix-transaction-sampler.spec.ts index e25ab30..d485090 100644 --- a/test/trace/samplers/coralogix-transaction-sampler.spec.ts +++ b/test/trace/samplers/coralogix-transaction-sampler.spec.ts @@ -273,6 +273,99 @@ export default describe('CoralogixTransactionSampler', () => { }); }) + describe('transaction root attribute', () => { + it('add transaction root attribute to creator of transaction', () => { + const tracerProvider = new BasicTracerProvider({ + sampler: new CoralogixTransactionSampler() + }); + const tracer = tracerProvider.getTracer('default'); + + const span1 = tracer.startSpan('one', {}, context); + context = opentelemetry.trace.setSpan(context, span1); + const span2 = tracer.startSpan('two', {}, context); + context = opentelemetry.trace.setSpan(context, span2); + const span3 = tracer.startSpan('three', {}, context); + context = opentelemetry.trace.setSpan(context, span3); + + if (span1 instanceof Span && span2 instanceof Span && span3 instanceof Span) { + assert.strictEqual(span1.attributes[CoralogixAttributes.TRANSACTION_ROOT], true, + 'span1 must have transaction root'); + assert.ok(!(CoralogixAttributes.TRANSACTION_ROOT in span2.attributes), + 'span2 must not have transaction root'); + assert.ok(!(CoralogixAttributes.TRANSACTION_ROOT in span3.attributes), + 'span3 must not have transaction root'); + } else { + assert.ok(span1 instanceof Span, 'span1 must be instance of Span'); + assert.ok(span2 instanceof Span, 'span2 must be instance of Span'); + assert.ok(span3 instanceof Span, 'span3 must be instance of Span'); + } + span3.end(); + span2.end(); + span1.end(); + }); + + it('add transaction root attribute span after remote', () => { + const tracerProvider = new BasicTracerProvider({ + sampler: new CoralogixTransactionSampler() + }); + const tracer = tracerProvider.getTracer('default'); + + const span1 = tracer.startSpan('one', {}, context); + context = opentelemetry.trace.setSpan(context, span1); + const span2 = tracer.startSpan('two', {}, context); + context = opentelemetry.trace.setSpan(context, span2); + context = getRemoteContext(context); + const span3 = tracer.startSpan('three', {}, context); + context = opentelemetry.trace.setSpan(context, span3); + const span4 = tracer.startSpan('four', {}, context); + context = opentelemetry.trace.setSpan(context, span4); + + if (span1 instanceof Span && span2 instanceof Span && span3 instanceof Span && span4 instanceof Span) { + assert.strictEqual(span1.attributes[CoralogixAttributes.TRANSACTION_ROOT], true, + 'span1 must have transaction root'); + assert.ok(!(CoralogixAttributes.TRANSACTION_ROOT in span2.attributes), + 'span2 must not have transaction root'); + assert.strictEqual(span3.attributes[CoralogixAttributes.TRANSACTION_ROOT], true, + 'span3 must have transaction root'); + assert.ok(!(CoralogixAttributes.TRANSACTION_ROOT in span4.attributes), + 'span4 must not have transaction root'); + } else { + assert.ok(span1 instanceof Span, 'span1 must be instance of Span'); + assert.ok(span2 instanceof Span, 'span2 must be instance of Span'); + assert.ok(span3 instanceof Span, 'span3 must be instance of Span'); + assert.ok(span4 instanceof Span, 'span4 must be instance of Span'); + } + span4.end(); + span3.end(); + span2.end(); + span1.end(); + }); + + it('span with same name as transaction span should not be root transaction', () => { + const tracerProvider = new BasicTracerProvider({ + sampler: new CoralogixTransactionSampler() + }); + const tracer = tracerProvider.getTracer('default'); + + const span1 = tracer.startSpan('one', {}, context); + context = opentelemetry.trace.setSpan(context, span1); + const span2 = tracer.startSpan('one', {}, context); + context = opentelemetry.trace.setSpan(context, span2); + + if (span1 instanceof Span && span2 instanceof Span) { + assert.strictEqual(span1.attributes[CoralogixAttributes.TRANSACTION_ROOT], true, + 'span1 must have transaction root'); + assert.ok(!(CoralogixAttributes.TRANSACTION_ROOT in span2.attributes), + 'span1 must not have transaction root'); + } else { + assert.ok(span1 instanceof Span, 'span1 must be instance of Span'); + assert.ok(span2 instanceof Span, 'span2 must be instance of Span'); + } + span2.end(); + span1.end(); + }); + }) + const NON_SAMPLED_ATTRIBUTE_NAME = 'non_sampled'; class TestAttributeSamplingSampler implements Sampler {