Skip to content
This repository was archived by the owner on Oct 3, 2023. It is now read-only.

Commit b5a49c1

Browse files
authored
Add HTTP/W3C text format serializer to Tag propagation component (#445)
* Add HTTP text format serializer to Tag propagation component * Fix link
1 parent ac0ef77 commit b5a49c1

File tree

4 files changed

+214
-0
lines changed

4 files changed

+214
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ All notable changes to this project will be documented in this file.
2222
- Enforce `--strictNullChecks` and `--noUnusedLocals` Compiler Options on [opencensus-exporter-jaeger] packages.
2323
- Add support for recording Exemplars.
2424
- Add `TagMetadata` that defines the properties associated with a `Tag`.
25+
- Add HTTP text format serializer to Tag propagation component.
2526

2627
## 0.0.9 - 2019-02-12
2728
- Add Metrics API.

packages/opencensus-core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export * from './stats/bucket-boundaries';
7272
export * from './stats/metric-utils';
7373
export * from './tags/tag-map';
7474
export * from './tags/tagger';
75+
export * from './tags/propagation/text-format';
7576
export * from './resource/resource';
7677

7778
// interfaces
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/**
2+
* Copyright 2019, OpenCensus Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* This module contains the functions for serializing and deserializing
19+
* TagMap (TagContext) with W3C Correlation Context as the HTTP text format.
20+
* It allows tags to propagate across requests.
21+
*
22+
* OpenCensus uses W3C Correlation Context as the HTTP text format.
23+
* https://github.com/w3c/correlation-context/blob/master/correlation_context/HTTP_HEADER_FORMAT.md
24+
*/
25+
26+
import {TagMap} from '../tag-map';
27+
import {TagKey, TagTtl, TagValue, TagValueWithMetadata} from '../types';
28+
29+
export const MAX_NUMBER_OF_TAGS = 180;
30+
const TAG_SERIALIZED_SIZE_LIMIT = 4096;
31+
const TAGMAP_SERIALIZED_SIZE_LIMIT = 8192;
32+
const TAG_KEY_VALUE_DELIMITER = '=';
33+
const TAG_DELIMITER = ',';
34+
const UNLIMITED_PROPAGATION_MD = {
35+
tagTtl: TagTtl.UNLIMITED_PROPAGATION
36+
};
37+
38+
/**
39+
* Serializes a given TagMap to the on-the-wire format based on the W3C HTTP
40+
* text format standard.
41+
* @param tagMap The TagMap to serialize.
42+
*/
43+
export function serializeTextFormat(tagMap: TagMap): string {
44+
let ret = '';
45+
let totalChars = 0;
46+
let totalTags = 0;
47+
const tags = tagMap.tagsWithMetadata;
48+
tags.forEach((tagsWithMetadata: TagValueWithMetadata, tagKey: TagKey) => {
49+
if (tagsWithMetadata.tagMetadata.tagTtl !== TagTtl.NO_PROPAGATION) {
50+
if (ret.length > 0) ret += TAG_DELIMITER;
51+
totalChars += validateTag(tagKey, tagsWithMetadata.tagValue);
52+
ret += tagKey.name + TAG_KEY_VALUE_DELIMITER +
53+
tagsWithMetadata.tagValue.value;
54+
totalTags++;
55+
}
56+
});
57+
58+
if (totalTags > MAX_NUMBER_OF_TAGS) {
59+
throw new Error(
60+
`Number of tags in the TagMap exceeds limit ${MAX_NUMBER_OF_TAGS}`);
61+
}
62+
63+
if (totalChars > TAGMAP_SERIALIZED_SIZE_LIMIT) {
64+
throw new Error(`Size of TagMap exceeds the maximum serialized size ${
65+
TAGMAP_SERIALIZED_SIZE_LIMIT}`);
66+
}
67+
68+
return ret;
69+
}
70+
71+
/**
72+
* Deserializes input to TagMap based on the W3C HTTP text format standard.
73+
* @param str The TagMap to deserialize.
74+
*/
75+
export function deserializeTextFormat(str: string): TagMap {
76+
const tags = new TagMap();
77+
if (!str) return tags;
78+
const listOfTags = str.split(TAG_DELIMITER);
79+
listOfTags.forEach((tag) => {
80+
const keyValuePair = tag.split(TAG_KEY_VALUE_DELIMITER);
81+
if (keyValuePair.length !== 2) throw new Error(`Malformed tag ${tag}`);
82+
83+
const [name, value] = keyValuePair;
84+
tags.set({name}, {value}, UNLIMITED_PROPAGATION_MD);
85+
});
86+
return tags;
87+
}
88+
89+
function validateTag(tagKey: TagKey, tagValue: TagValue) {
90+
const charsOfTag = tagKey.name.length + tagValue.value.length;
91+
if (charsOfTag > TAG_SERIALIZED_SIZE_LIMIT) {
92+
throw new Error(
93+
`Serialized size of tag exceeds limit ${TAG_SERIALIZED_SIZE_LIMIT}`);
94+
}
95+
return charsOfTag;
96+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/**
2+
* Copyright 2019, OpenCensus Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import * as assert from 'assert';
18+
import {TagMap, TagTtl} from '../src';
19+
import {deserializeTextFormat, MAX_NUMBER_OF_TAGS, serializeTextFormat} from '../src/tags/propagation/text-format';
20+
21+
const K1 = {
22+
name: 'k1'
23+
};
24+
const K2 = {
25+
name: 'k2'
26+
};
27+
28+
const V1 = {
29+
value: 'v1'
30+
};
31+
const V2 = {
32+
value: 'v2'
33+
};
34+
35+
describe('Text Format Serializer', () => {
36+
const emptyTagMap = new TagMap();
37+
38+
const singleTagMap = new TagMap();
39+
singleTagMap.set(K1, V1);
40+
41+
const multipleTagMap = new TagMap();
42+
multipleTagMap.set(K1, V1);
43+
multipleTagMap.set(K2, V2);
44+
45+
const NO_PROPAGATION_MD = {tagTtl: TagTtl.NO_PROPAGATION};
46+
const nonPropagatingTagMap = new TagMap();
47+
nonPropagatingTagMap.set(K1, V1, NO_PROPAGATION_MD);
48+
49+
describe('serializeTextFormat', () => {
50+
it('should serialize empty tag map', () => {
51+
const textFormat = serializeTextFormat(emptyTagMap);
52+
assert.equal(textFormat, '');
53+
});
54+
55+
it('should serialize with one tag map', () => {
56+
const textFormat = serializeTextFormat(singleTagMap);
57+
assert.deepEqual(textFormat, 'k1=v1');
58+
});
59+
60+
it('should serialize with multiple tag', () => {
61+
const textFormat = serializeTextFormat(multipleTagMap);
62+
assert.deepEqual(textFormat, 'k1=v1,k2=v2');
63+
});
64+
65+
it('should skip non propagating tag', () => {
66+
const textFormat = serializeTextFormat(nonPropagatingTagMap);
67+
assert.deepEqual(textFormat, '');
68+
});
69+
70+
it('should throw an error when exceeds the max number of tags', () => {
71+
const tags = new TagMap();
72+
for (let i = 0; i < MAX_NUMBER_OF_TAGS + 1; i++) {
73+
tags.set({name: `name-${i}`}, {value: `value-${i}`});
74+
}
75+
76+
assert.throws(() => {
77+
serializeTextFormat(tags);
78+
}, /^Error: Number of tags in the TagMap exceeds limit 180/);
79+
});
80+
});
81+
82+
describe('deserializeTextFormat', () => {
83+
it('should deserialize empty string', () => {
84+
const deserializedTagMap = deserializeTextFormat('');
85+
assert.deepEqual(deserializedTagMap.tags.size, 0);
86+
});
87+
88+
it('should deserialize with one key value pair', () => {
89+
const deserializedTagMap = deserializeTextFormat('k1=v1');
90+
assert.deepEqual(deserializedTagMap.tags.size, 1);
91+
assert.deepEqual(deserializedTagMap, singleTagMap);
92+
});
93+
94+
it('should deserialize with multiple pairs', () => {
95+
const deserializedTagMap = deserializeTextFormat('k1=v1,k2=v2');
96+
assert.deepEqual(deserializedTagMap.tags.size, 2);
97+
assert.deepEqual(deserializedTagMap, multipleTagMap);
98+
});
99+
100+
it('should deserialize with white spaces tag', () => {
101+
const expectedTagMap = new TagMap();
102+
expectedTagMap.set(K1, {value: ' v1'});
103+
expectedTagMap.set({name: ' k2'}, {value: 'v 2'});
104+
105+
const deserializedTagMap = deserializeTextFormat('k1= v1, k2=v 2');
106+
assert.deepEqual(deserializedTagMap.tags.size, 2);
107+
assert.deepEqual(deserializedTagMap, expectedTagMap);
108+
});
109+
110+
it('should throw an error when tags are malformed', () => {
111+
assert.throws(() => {
112+
deserializeTextFormat('k1,v1,k2=v2');
113+
}, /^Error: Malformed tag k1/);
114+
});
115+
});
116+
});

0 commit comments

Comments
 (0)