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

Commit

Permalink
Add HTTP/W3C text format serializer to Tag propagation component (#445)
Browse files Browse the repository at this point in the history
* Add HTTP text format serializer to Tag propagation component

* Fix link
  • Loading branch information
mayurkale22 authored Mar 26, 2019
1 parent ac0ef77 commit b5a49c1
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ All notable changes to this project will be documented in this file.
- Enforce `--strictNullChecks` and `--noUnusedLocals` Compiler Options on [opencensus-exporter-jaeger] packages.
- Add support for recording Exemplars.
- Add `TagMetadata` that defines the properties associated with a `Tag`.
- Add HTTP text format serializer to Tag propagation component.

## 0.0.9 - 2019-02-12
- Add Metrics API.
Expand Down
1 change: 1 addition & 0 deletions packages/opencensus-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export * from './stats/bucket-boundaries';
export * from './stats/metric-utils';
export * from './tags/tag-map';
export * from './tags/tagger';
export * from './tags/propagation/text-format';
export * from './resource/resource';

// interfaces
Expand Down
96 changes: 96 additions & 0 deletions packages/opencensus-core/src/tags/propagation/text-format.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* Copyright 2019, OpenCensus Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* This module contains the functions for serializing and deserializing
* TagMap (TagContext) with W3C Correlation Context as the HTTP text format.
* It allows tags to propagate across requests.
*
* OpenCensus uses W3C Correlation Context as the HTTP text format.
* https://github.com/w3c/correlation-context/blob/master/correlation_context/HTTP_HEADER_FORMAT.md
*/

import {TagMap} from '../tag-map';
import {TagKey, TagTtl, TagValue, TagValueWithMetadata} from '../types';

export const MAX_NUMBER_OF_TAGS = 180;
const TAG_SERIALIZED_SIZE_LIMIT = 4096;
const TAGMAP_SERIALIZED_SIZE_LIMIT = 8192;
const TAG_KEY_VALUE_DELIMITER = '=';
const TAG_DELIMITER = ',';
const UNLIMITED_PROPAGATION_MD = {
tagTtl: TagTtl.UNLIMITED_PROPAGATION
};

/**
* Serializes a given TagMap to the on-the-wire format based on the W3C HTTP
* text format standard.
* @param tagMap The TagMap to serialize.
*/
export function serializeTextFormat(tagMap: TagMap): string {
let ret = '';
let totalChars = 0;
let totalTags = 0;
const tags = tagMap.tagsWithMetadata;
tags.forEach((tagsWithMetadata: TagValueWithMetadata, tagKey: TagKey) => {
if (tagsWithMetadata.tagMetadata.tagTtl !== TagTtl.NO_PROPAGATION) {
if (ret.length > 0) ret += TAG_DELIMITER;
totalChars += validateTag(tagKey, tagsWithMetadata.tagValue);
ret += tagKey.name + TAG_KEY_VALUE_DELIMITER +
tagsWithMetadata.tagValue.value;
totalTags++;
}
});

if (totalTags > MAX_NUMBER_OF_TAGS) {
throw new Error(
`Number of tags in the TagMap exceeds limit ${MAX_NUMBER_OF_TAGS}`);
}

if (totalChars > TAGMAP_SERIALIZED_SIZE_LIMIT) {
throw new Error(`Size of TagMap exceeds the maximum serialized size ${
TAGMAP_SERIALIZED_SIZE_LIMIT}`);
}

return ret;
}

/**
* Deserializes input to TagMap based on the W3C HTTP text format standard.
* @param str The TagMap to deserialize.
*/
export function deserializeTextFormat(str: string): TagMap {
const tags = new TagMap();
if (!str) return tags;
const listOfTags = str.split(TAG_DELIMITER);
listOfTags.forEach((tag) => {
const keyValuePair = tag.split(TAG_KEY_VALUE_DELIMITER);
if (keyValuePair.length !== 2) throw new Error(`Malformed tag ${tag}`);

const [name, value] = keyValuePair;
tags.set({name}, {value}, UNLIMITED_PROPAGATION_MD);
});
return tags;
}

function validateTag(tagKey: TagKey, tagValue: TagValue) {
const charsOfTag = tagKey.name.length + tagValue.value.length;
if (charsOfTag > TAG_SERIALIZED_SIZE_LIMIT) {
throw new Error(
`Serialized size of tag exceeds limit ${TAG_SERIALIZED_SIZE_LIMIT}`);
}
return charsOfTag;
}
116 changes: 116 additions & 0 deletions packages/opencensus-core/test/test-text-format.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/**
* Copyright 2019, OpenCensus Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as assert from 'assert';
import {TagMap, TagTtl} from '../src';
import {deserializeTextFormat, MAX_NUMBER_OF_TAGS, serializeTextFormat} from '../src/tags/propagation/text-format';

const K1 = {
name: 'k1'
};
const K2 = {
name: 'k2'
};

const V1 = {
value: 'v1'
};
const V2 = {
value: 'v2'
};

describe('Text Format Serializer', () => {
const emptyTagMap = new TagMap();

const singleTagMap = new TagMap();
singleTagMap.set(K1, V1);

const multipleTagMap = new TagMap();
multipleTagMap.set(K1, V1);
multipleTagMap.set(K2, V2);

const NO_PROPAGATION_MD = {tagTtl: TagTtl.NO_PROPAGATION};
const nonPropagatingTagMap = new TagMap();
nonPropagatingTagMap.set(K1, V1, NO_PROPAGATION_MD);

describe('serializeTextFormat', () => {
it('should serialize empty tag map', () => {
const textFormat = serializeTextFormat(emptyTagMap);
assert.equal(textFormat, '');
});

it('should serialize with one tag map', () => {
const textFormat = serializeTextFormat(singleTagMap);
assert.deepEqual(textFormat, 'k1=v1');
});

it('should serialize with multiple tag', () => {
const textFormat = serializeTextFormat(multipleTagMap);
assert.deepEqual(textFormat, 'k1=v1,k2=v2');
});

it('should skip non propagating tag', () => {
const textFormat = serializeTextFormat(nonPropagatingTagMap);
assert.deepEqual(textFormat, '');
});

it('should throw an error when exceeds the max number of tags', () => {
const tags = new TagMap();
for (let i = 0; i < MAX_NUMBER_OF_TAGS + 1; i++) {
tags.set({name: `name-${i}`}, {value: `value-${i}`});
}

assert.throws(() => {
serializeTextFormat(tags);
}, /^Error: Number of tags in the TagMap exceeds limit 180/);
});
});

describe('deserializeTextFormat', () => {
it('should deserialize empty string', () => {
const deserializedTagMap = deserializeTextFormat('');
assert.deepEqual(deserializedTagMap.tags.size, 0);
});

it('should deserialize with one key value pair', () => {
const deserializedTagMap = deserializeTextFormat('k1=v1');
assert.deepEqual(deserializedTagMap.tags.size, 1);
assert.deepEqual(deserializedTagMap, singleTagMap);
});

it('should deserialize with multiple pairs', () => {
const deserializedTagMap = deserializeTextFormat('k1=v1,k2=v2');
assert.deepEqual(deserializedTagMap.tags.size, 2);
assert.deepEqual(deserializedTagMap, multipleTagMap);
});

it('should deserialize with white spaces tag', () => {
const expectedTagMap = new TagMap();
expectedTagMap.set(K1, {value: ' v1'});
expectedTagMap.set({name: ' k2'}, {value: 'v 2'});

const deserializedTagMap = deserializeTextFormat('k1= v1, k2=v 2');
assert.deepEqual(deserializedTagMap.tags.size, 2);
assert.deepEqual(deserializedTagMap, expectedTagMap);
});

it('should throw an error when tags are malformed', () => {
assert.throws(() => {
deserializeTextFormat('k1,v1,k2=v2');
}, /^Error: Malformed tag k1/);
});
});
});

0 comments on commit b5a49c1

Please sign in to comment.