Skip to content

Commit 23677fd

Browse files
feat(opentelemetry-resources): add schema url (open-telemetry#5753)
Co-authored-by: Marc Pichler <[email protected]>
1 parent 60b701d commit 23677fd

File tree

13 files changed

+429
-76
lines changed

13 files changed

+429
-76
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ For notes on migrating to 2.x / 0.200.x see [the upgrade guide](doc/upgrade-to-2
1616

1717
### :rocket: Features
1818

19+
* feat(opentelemetry-resources): add schema url [#5070](https://github.com/open-telemetry/opentelemetry-js/pull/5753) @c-ehrlich
20+
1921
### :bug: Bug Fixes
2022

2123
* fix(sdk-metrics): Remove invalid default value for `startTime` param to ExponentialHistogramAccumulation. This only impacted the closurescript compiler. [#5763](https://github.com/open-telemetry/opentelemetry-js/pull/5763) @trentm

experimental/packages/otlp-transformer/src/common/internal-types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ export interface Resource {
2121

2222
/** Resource droppedAttributesCount */
2323
droppedAttributesCount: number;
24+
25+
/** Resource schemaUrl */
26+
schemaUrl?: string;
2427
}
2528

2629
/** Properties of an InstrumentationScope. */

experimental/packages/otlp-transformer/src/common/internal.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,15 @@ import { InstrumentationScope } from '@opentelemetry/core';
2424
import { Resource as ISdkResource } from '@opentelemetry/resources';
2525

2626
export function createResource(resource: ISdkResource): Resource {
27-
return {
27+
const result: Resource = {
2828
attributes: toAttributes(resource.attributes),
2929
droppedAttributesCount: 0,
3030
};
31+
32+
const schemaUrl = resource.schemaUrl;
33+
if (schemaUrl && schemaUrl !== '') result.schemaUrl = schemaUrl;
34+
35+
return result;
3136
}
3237

3338
export function createInstrumentationScope(

experimental/packages/otlp-transformer/src/logs/internal.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -79,17 +79,20 @@ function logRecordsToResourceLogs(
7979
encoder: Encoder
8080
): IResourceLogs[] {
8181
const resourceMap = createResourceMap(logRecords);
82-
return Array.from(resourceMap, ([resource, ismMap]) => ({
83-
resource: createResource(resource),
84-
scopeLogs: Array.from(ismMap, ([, scopeLogs]) => {
85-
return {
86-
scope: createInstrumentationScope(scopeLogs[0].instrumentationScope),
87-
logRecords: scopeLogs.map(log => toLogRecord(log, encoder)),
88-
schemaUrl: scopeLogs[0].instrumentationScope.schemaUrl,
89-
};
90-
}),
91-
schemaUrl: undefined,
92-
}));
82+
return Array.from(resourceMap, ([resource, ismMap]) => {
83+
const processedResource = createResource(resource);
84+
return {
85+
resource: processedResource,
86+
scopeLogs: Array.from(ismMap, ([, scopeLogs]) => {
87+
return {
88+
scope: createInstrumentationScope(scopeLogs[0].instrumentationScope),
89+
logRecords: scopeLogs.map(log => toLogRecord(log, encoder)),
90+
schemaUrl: scopeLogs[0].instrumentationScope.schemaUrl,
91+
};
92+
}),
93+
schemaUrl: processedResource.schemaUrl,
94+
};
95+
});
9396
}
9497

9598
function toLogRecord(log: ReadableLogRecord, encoder: Encoder): ILogRecord {

experimental/packages/otlp-transformer/src/metrics/internal.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,10 @@ export function toResourceMetrics(
4747
options?: OtlpEncodingOptions
4848
): IResourceMetrics {
4949
const encoder = getOtlpEncoder(options);
50+
const processedResource = createResource(resourceMetrics.resource);
5051
return {
51-
resource: createResource(resourceMetrics.resource),
52-
schemaUrl: undefined,
52+
resource: processedResource,
53+
schemaUrl: processedResource.schemaUrl,
5354
scopeMetrics: toScopeMetrics(resourceMetrics.scopeMetrics, encoder),
5455
};
5556
}

experimental/packages/otlp-transformer/src/trace/internal.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,11 +170,11 @@ function spanRecordsToResourceSpans(
170170
}
171171
ilmEntry = ilmIterator.next();
172172
}
173-
// TODO SDK types don't provide resource schema URL at this time
173+
const processedResource = createResource(resource);
174174
const transformedSpans: IResourceSpans = {
175-
resource: createResource(resource),
175+
resource: processedResource,
176176
scopeSpans: scopeResourceSpans,
177-
schemaUrl: undefined,
177+
schemaUrl: processedResource.schemaUrl,
178178
};
179179

180180
out.push(transformedSpans);

experimental/packages/otlp-transformer/test/logs.test.ts

Lines changed: 64 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,27 @@ function createExpectedLogProtobuf(): IExportLogsServiceRequest {
144144
};
145145
}
146146

147+
const DEFAULT_LOG_FRAGMENT: Omit<
148+
ReadableLogRecord,
149+
'resource' | 'instrumentationScope'
150+
> = {
151+
hrTime: [1680253513, 123241635] as HrTime,
152+
hrTimeObserved: [1683526948, 965142784] as HrTime,
153+
attributes: {
154+
'some-attribute': 'some attribute value',
155+
},
156+
droppedAttributesCount: 0,
157+
severityNumber: SeverityNumber.ERROR,
158+
severityText: 'error',
159+
body: 'some_log_body',
160+
eventName: 'some.event.name',
161+
spanContext: {
162+
spanId: '0000000000000002',
163+
traceFlags: TraceFlags.SAMPLED,
164+
traceId: '00000000000000000000000000000001',
165+
},
166+
} as const;
167+
147168
describe('Logs', () => {
148169
let resource_1: Resource;
149170
let resource_2: Resource;
@@ -166,6 +187,18 @@ describe('Logs', () => {
166187
// using `resource_2`, `scope_1`, `log_fragment_1`
167188
let log_2_1_1: ReadableLogRecord;
168189

190+
function createReadableLogRecord(
191+
resource: Resource,
192+
scope: InstrumentationScope,
193+
logFragment: Omit<ReadableLogRecord, 'resource' | 'instrumentationScope'>
194+
): ReadableLogRecord {
195+
return {
196+
...logFragment,
197+
resource: resource,
198+
instrumentationScope: scope,
199+
} as ReadableLogRecord;
200+
}
201+
169202
beforeEach(() => {
170203
resource_1 = resourceFromAttributes({
171204
'resource-attribute': 'some attribute value',
@@ -181,23 +214,8 @@ describe('Logs', () => {
181214
scope_2 = {
182215
name: 'scope_name_2',
183216
};
184-
const log_fragment_1 = {
185-
hrTime: [1680253513, 123241635] as HrTime,
186-
hrTimeObserved: [1683526948, 965142784] as HrTime,
187-
attributes: {
188-
'some-attribute': 'some attribute value',
189-
},
190-
droppedAttributesCount: 0,
191-
severityNumber: SeverityNumber.ERROR,
192-
severityText: 'error',
193-
body: 'some_log_body',
194-
eventName: 'some.event.name',
195-
spanContext: {
196-
spanId: '0000000000000002',
197-
traceFlags: TraceFlags.SAMPLED,
198-
traceId: '00000000000000000000000000000001',
199-
},
200-
};
217+
218+
const log_fragment_1 = DEFAULT_LOG_FRAGMENT;
201219
const log_fragment_2 = {
202220
hrTime: [1680253797, 687038506] as HrTime,
203221
hrTimeObserved: [1680253797, 687038506] as HrTime,
@@ -206,26 +224,11 @@ describe('Logs', () => {
206224
},
207225
droppedAttributesCount: 0,
208226
};
209-
log_1_1_1 = {
210-
...log_fragment_1,
211-
resource: resource_1,
212-
instrumentationScope: scope_1,
213-
};
214-
log_1_1_2 = {
215-
...log_fragment_2,
216-
resource: resource_1,
217-
instrumentationScope: scope_1,
218-
};
219-
log_1_2_1 = {
220-
...log_fragment_1,
221-
resource: resource_1,
222-
instrumentationScope: scope_2,
223-
};
224-
log_2_1_1 = {
225-
...log_fragment_1,
226-
resource: resource_2,
227-
instrumentationScope: scope_1,
228-
};
227+
228+
log_1_1_1 = createReadableLogRecord(resource_1, scope_1, log_fragment_1);
229+
log_1_1_2 = createReadableLogRecord(resource_1, scope_1, log_fragment_2);
230+
log_1_2_1 = createReadableLogRecord(resource_1, scope_2, log_fragment_1);
231+
log_2_1_1 = createReadableLogRecord(resource_2, scope_1, log_fragment_1);
229232
});
230233

231234
describe('createExportLogsServiceRequest', () => {
@@ -292,6 +295,30 @@ describe('Logs', () => {
292295
assert.ok(exportRequest);
293296
assert.strictEqual(exportRequest.resourceLogs?.length, 2);
294297
});
298+
299+
it('supports schema URL on resource', () => {
300+
const resourceWithSchema = resourceFromAttributes(
301+
{},
302+
{ schemaUrl: 'https://opentelemetry.test/schemas/1.2.3' }
303+
);
304+
305+
const logWithSchema = createReadableLogRecord(
306+
resourceWithSchema,
307+
scope_1,
308+
DEFAULT_LOG_FRAGMENT
309+
);
310+
311+
const exportRequest = createExportLogsServiceRequest([logWithSchema], {
312+
useHex: true,
313+
});
314+
315+
assert.ok(exportRequest);
316+
assert.strictEqual(exportRequest.resourceLogs?.length, 1);
317+
assert.strictEqual(
318+
exportRequest.resourceLogs?.[0].schemaUrl,
319+
'https://opentelemetry.test/schemas/1.2.3'
320+
);
321+
});
295322
});
296323

297324
describe('ProtobufLogsSerializer', function () {

experimental/packages/otlp-transformer/test/metrics.test.ts

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616
import { ValueType } from '@opentelemetry/api';
17-
import { resourceFromAttributes } from '@opentelemetry/resources';
17+
import { Resource, resourceFromAttributes } from '@opentelemetry/resources';
1818
import {
1919
AggregationTemporality,
2020
DataPointType,
@@ -301,10 +301,16 @@ describe('Metrics', () => {
301301
};
302302
}
303303

304-
function createResourceMetrics(metricData: MetricData[]): ResourceMetrics {
305-
const resource = resourceFromAttributes({
306-
'resource-attribute': 'resource attribute value',
307-
});
304+
function createResourceMetrics(
305+
metricData: MetricData[],
306+
customResource?: Resource
307+
): ResourceMetrics {
308+
const resource =
309+
customResource ||
310+
resourceFromAttributes({
311+
'resource-attribute': 'resource attribute value',
312+
});
313+
308314
return {
309315
resource: resource,
310316
scopeMetrics: [
@@ -773,6 +779,29 @@ describe('Metrics', () => {
773779
});
774780
});
775781
});
782+
783+
it('supports schema URL on resource', () => {
784+
const resourceWithSchema = resourceFromAttributes(
785+
{},
786+
{ schemaUrl: 'https://opentelemetry.test/schemas/1.2.3' }
787+
);
788+
789+
const resourceMetrics = createResourceMetrics(
790+
[createCounterData(10, AggregationTemporality.DELTA)],
791+
resourceWithSchema
792+
);
793+
794+
const exportRequest = createExportMetricsServiceRequest([
795+
resourceMetrics,
796+
]);
797+
798+
assert.ok(exportRequest);
799+
assert.strictEqual(exportRequest.resourceMetrics?.length, 1);
800+
assert.strictEqual(
801+
exportRequest.resourceMetrics?.[0].schemaUrl,
802+
'https://opentelemetry.test/schemas/1.2.3'
803+
);
804+
});
776805
});
777806

778807
describe('ProtobufMetricsSerializer', function () {

experimental/packages/otlp-transformer/test/trace.test.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -232,11 +232,8 @@ describe('Trace', () => {
232232
let resource: Resource;
233233
let span: ReadableSpan;
234234

235-
beforeEach(() => {
236-
resource = resourceFromAttributes({
237-
'resource-attribute': 'resource attribute value',
238-
});
239-
span = {
235+
function createSpanWithResource(spanResource: Resource): ReadableSpan {
236+
return {
240237
spanContext: () => ({
241238
spanId: '0000000000000002',
242239
traceFlags: TraceFlags.SAMPLED,
@@ -283,7 +280,7 @@ describe('Trace', () => {
283280
},
284281
],
285282
name: 'span-name',
286-
resource,
283+
resource: spanResource,
287284
startTime: [1640715557, 342725388],
288285
status: {
289286
code: SpanStatusCode.OK,
@@ -292,6 +289,13 @@ describe('Trace', () => {
292289
droppedEventsCount: 0,
293290
droppedLinksCount: 0,
294291
};
292+
}
293+
294+
beforeEach(() => {
295+
resource = resourceFromAttributes({
296+
'resource-attribute': 'resource attribute value',
297+
});
298+
span = createSpanWithResource(resource);
295299
});
296300

297301
describe('createExportTraceServiceRequest', () => {
@@ -443,6 +447,26 @@ describe('Trace', () => {
443447
);
444448
});
445449
});
450+
451+
it('supports schema URL on resource', () => {
452+
const resourceWithSchema = resourceFromAttributes(
453+
{ 'resource-attribute': 'resource attribute value' },
454+
{ schemaUrl: 'https://opentelemetry.test/schemas/1.2.3' }
455+
);
456+
457+
const spanFromSDK = createSpanWithResource(resourceWithSchema);
458+
459+
const exportRequest = createExportTraceServiceRequest([spanFromSDK], {
460+
useHex: true,
461+
});
462+
463+
assert.ok(exportRequest);
464+
assert.strictEqual(exportRequest.resourceSpans?.length, 1);
465+
assert.strictEqual(
466+
exportRequest.resourceSpans?.[0].schemaUrl,
467+
'https://opentelemetry.test/schemas/1.2.3'
468+
);
469+
});
446470
});
447471

448472
describe('ProtobufTracesSerializer', function () {

packages/opentelemetry-resources/src/Resource.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ export interface Resource {
4141
*/
4242
readonly attributes: Attributes;
4343

44+
/**
45+
* @returns the Resource's schema URL or undefined if not set.
46+
*/
47+
readonly schemaUrl?: string;
48+
4449
/**
4550
* Returns a promise that will never be rejected. Resolves when all async attributes have finished being added to
4651
* this Resource's attributes. This is useful in exporters to block until resource detection

0 commit comments

Comments
 (0)