From fba5038da1bfa23109666c750ae376ce4d959f73 Mon Sep 17 00:00:00 2001 From: Carlo Pearson Date: Thu, 15 Oct 2020 17:28:19 -0700 Subject: [PATCH 1/8] feat(opentelemetry-js): extract content-length header for span attrs Signed-off-by: Carlo Pearson --- .../opentelemetry-plugin-http/src/utils.ts | 38 ++++++++++++++++++- .../test/functionals/utils.test.ts | 15 +++++++- .../src/trace/http.ts | 3 ++ 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/packages/opentelemetry-plugin-http/src/utils.ts b/packages/opentelemetry-plugin-http/src/utils.ts index cfe8d9515e..1fb1cbdc99 100644 --- a/packages/opentelemetry-plugin-http/src/utils.ts +++ b/packages/opentelemetry-plugin-http/src/utils.ts @@ -217,6 +217,36 @@ export const setSpanWithError = ( span.setStatus(status); }; +/** + * + * @param headers + * @param Attributes + */ +export const setRequestContentLengthAttributes = ( + headers: OutgoingHttpHeaders | IncomingHttpHeaders, + attributes: Attributes +) => { + let isCompressed = false + + if (headers['content-encoding'] && headers['content-encoding'] !== 'identity') { + isCompressed = true + } + + if (isCompressed) { + if (headers as OutgoingHttpHeaders) { + attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH] = headers['content-length'] + } else { + attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH] = headers['content-length'] + } + } else { + if (headers as OutgoingHttpHeaders) { + attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED] = headers['content-length'] + } else { + attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED] = headers['content-length'] + } + } +}; + /** * Makes sure options is an url object * return an object with default value and parsed options @@ -356,14 +386,18 @@ export const getOutgoingRequestAttributesOnResponse = ( response: IncomingMessage, options: { hostname: string } ): Attributes => { - const { statusCode, statusMessage, httpVersion, socket } = response; + const { statusCode, statusMessage, httpVersion, headers, socket } = response; const { remoteAddress, remotePort } = socket; + + const attributes: Attributes = { [GeneralAttribute.NET_PEER_IP]: remoteAddress, [GeneralAttribute.NET_PEER_PORT]: remotePort, [HttpAttribute.HTTP_HOST]: `${options.hostname}:${remotePort}`, }; + // Place content-length func here? + if (statusCode) { attributes[HttpAttribute.HTTP_STATUS_CODE] = statusCode; attributes[HttpAttribute.HTTP_STATUS_TEXT] = ( @@ -424,6 +458,8 @@ export const getIncomingRequestAttributes = ( attributes[HttpAttribute.HTTP_USER_AGENT] = userAgent; } + // Place content-length func here? + const httpKindAttributes = getAttributesFromHttpKind(httpVersion); return Object.assign(attributes, httpKindAttributes); }; diff --git a/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts b/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts index e69d6b7252..9a290e551a 100644 --- a/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts +++ b/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { CanonicalCode, SpanKind, TraceFlags } from '@opentelemetry/api'; +import { Attributes, CanonicalCode, SpanKind, TraceFlags } from '@opentelemetry/api'; import { NoopLogger } from '@opentelemetry/core'; import { BasicTracerProvider, Span } from '@opentelemetry/tracing'; import { HttpAttribute } from '@opentelemetry/semantic-conventions'; @@ -309,4 +309,17 @@ describe('Utility', () => { assert.deepEqual(attributes[HttpAttribute.HTTP_ROUTE], undefined); }); }); + + describe('setRequestContentLengthAttributes', () => { + it('should set attributes', () => { + const attributes: Attributes = {} + let headers: http.OutgoingHttpHeaders = { + 'content-length': 1200 + } + + utils.setRequestContentLengthAttributes(headers, attributes) + + assert.strictEqual(attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], 1200) + }) + }) }); diff --git a/packages/opentelemetry-semantic-conventions/src/trace/http.ts b/packages/opentelemetry-semantic-conventions/src/trace/http.ts index d54166c545..ae8e7e657b 100644 --- a/packages/opentelemetry-semantic-conventions/src/trace/http.ts +++ b/packages/opentelemetry-semantic-conventions/src/trace/http.ts @@ -26,6 +26,9 @@ export const HttpAttribute = { HTTP_CLIENT_IP: 'http.client_ip', HTTP_SCHEME: 'http.scheme', HTTP_RESPONSE_CONTENT_LENGTH: 'http.response_content_length', + HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED: 'http.request_content_length_uncompressed', + HTTP_REQUEST_CONTENT_LENGTH: 'http.response_content_length', + HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED: 'http.request_content_length_uncompressed', // NOT ON OFFICIAL SPEC HTTP_ERROR_NAME: 'http.error_name', From 97c87993090c1a6b9d3bd53cd311c0e5d1240df5 Mon Sep 17 00:00:00 2001 From: Carlo Pearson Date: Thu, 22 Oct 2020 16:25:17 -0700 Subject: [PATCH 2/8] feat(opentelemetry-js): add content-length attributes to HTTP spans Signed-off-by: Carlo Pearson --- .../opentelemetry-plugin-http/src/utils.ts | 33 ++--- .../test/functionals/utils.test.ts | 118 ++++++++++++++++-- .../test/utils/assertSpan.ts | 33 +++++ .../src/trace/http.ts | 4 +- 4 files changed, 162 insertions(+), 26 deletions(-) diff --git a/packages/opentelemetry-plugin-http/src/utils.ts b/packages/opentelemetry-plugin-http/src/utils.ts index 1fb1cbdc99..a523909604 100644 --- a/packages/opentelemetry-plugin-http/src/utils.ts +++ b/packages/opentelemetry-plugin-http/src/utils.ts @@ -218,33 +218,38 @@ export const setSpanWithError = ( }; /** - * - * @param headers - * @param Attributes + * Adds attributes for content-length and content-encoding HTTP headers + * @param { OutgoingHttpHeaders | IncomingHttpHeaders } headers http headers + * @param { Attributes } Attributes span attributes + * @param { boolean } isRequest set true for setting request content-header */ -export const setRequestContentLengthAttributes = ( +export const setContentLengthAttributes = ( headers: OutgoingHttpHeaders | IncomingHttpHeaders, - attributes: Attributes + attributes: Attributes, + isRequest: boolean ) => { - let isCompressed = false + let isCompressed = false; + + if (headers['content-length'] === undefined) + return; if (headers['content-encoding'] && headers['content-encoding'] !== 'identity') { isCompressed = true - } + }; if (isCompressed) { - if (headers as OutgoingHttpHeaders) { + if (isRequest) { attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH] = headers['content-length'] } else { attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH] = headers['content-length'] - } + }; } else { - if (headers as OutgoingHttpHeaders) { + if (isRequest) { attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED] = headers['content-length'] } else { attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED] = headers['content-length'] - } - } + }; + }; }; /** @@ -396,7 +401,7 @@ export const getOutgoingRequestAttributesOnResponse = ( [HttpAttribute.HTTP_HOST]: `${options.hostname}:${remotePort}`, }; - // Place content-length func here? + setContentLengthAttributes(headers, attributes, false) if (statusCode) { attributes[HttpAttribute.HTTP_STATUS_CODE] = statusCode; @@ -458,7 +463,7 @@ export const getIncomingRequestAttributes = ( attributes[HttpAttribute.HTTP_USER_AGENT] = userAgent; } - // Place content-length func here? + setContentLengthAttributes(headers, attributes, true) const httpKindAttributes = getAttributesFromHttpKind(httpVersion); return Object.assign(attributes, httpKindAttributes); diff --git a/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts b/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts index 9a290e551a..01f8744e38 100644 --- a/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts +++ b/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts @@ -310,16 +310,114 @@ describe('Utility', () => { }); }); - describe('setRequestContentLengthAttributes', () => { - it('should set attributes', () => { - const attributes: Attributes = {} - let headers: http.OutgoingHttpHeaders = { - 'content-length': 1200 - } + describe('setContentLengthAttributes', () => { + it('should set response content-length uncompressed attribute with no content-encoding header', () => { + const attributes: Attributes = {}; + + let headers: http.IncomingHttpHeaders = {}; + + headers["content-length"] = '1200'; + + utils.setContentLengthAttributes(headers, attributes, false); + + assert.strictEqual(attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED], '1200'); + assert.strictEqual(attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], undefined); + assert.strictEqual(attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], undefined); + assert.strictEqual(attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH], undefined); + }); + + it('should set response content-length uncompressed attribute with "identity" content-encoding header', () => { + const attributes: Attributes = {}; + + let headers: http.IncomingHttpHeaders = {}; + + headers["content-length"] = '1200'; + headers["content-encoding"] = 'identity'; + + utils.setContentLengthAttributes(headers, attributes, false); + + assert.strictEqual(attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED], '1200'); + assert.strictEqual(attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], undefined); + assert.strictEqual(attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], undefined); + assert.strictEqual(attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH], undefined); + }); + + it('should set response content-length compressed attribute with "gzip" content-encoding header', () => { + const attributes: Attributes = {}; + + let headers: http.IncomingHttpHeaders = {}; + + headers["content-length"] = '1200'; + headers["content-encoding"] = 'gzip'; + + utils.setContentLengthAttributes(headers, attributes, false); + + assert.strictEqual(attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], '1200'); + assert.strictEqual(attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED], undefined); + assert.strictEqual(attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], undefined); + assert.strictEqual(attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH], undefined); + }); + + it('should set request content-length uncompressed attribute with no content-encoding header', () => { + const attributes: Attributes = {}; + + let headers: http.OutgoingHttpHeaders = {}; + + headers["content-length"] = '1200'; - utils.setRequestContentLengthAttributes(headers, attributes) + utils.setContentLengthAttributes(headers, attributes, true); - assert.strictEqual(attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], 1200) - }) - }) + assert.strictEqual(attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], '1200'); + assert.strictEqual(attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH], undefined); + assert.strictEqual(attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED], undefined); + assert.strictEqual(attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], undefined); + }); + + it('should set request content-length uncompressed attribute with "identity" content-encoding header', () => { + const attributes: Attributes = {}; + + let headers: http.OutgoingHttpHeaders = {}; + + headers["content-length"] = '1200'; + headers["content-encoding"] = 'identity'; + + utils.setContentLengthAttributes(headers, attributes, true); + + assert.strictEqual(attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], '1200'); + assert.strictEqual(attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH], undefined); + assert.strictEqual(attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED], undefined); + assert.strictEqual(attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], undefined); + }); + + it('should set request content-length compressed attribute with "gzip" content-encoding header', () => { + const attributes: Attributes = {}; + + let headers: http.OutgoingHttpHeaders = {}; + + headers["content-length"] = '1200'; + headers["content-encoding"] = 'gzip'; + + utils.setContentLengthAttributes(headers, attributes, true); + + assert.strictEqual(attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH], '1200'); + assert.strictEqual(attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], undefined); + assert.strictEqual(attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED], undefined); + assert.strictEqual(attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], undefined); + }); + + it('should set no attributes with no content-length header', () => { + const attributes: Attributes = {}; + + let headers: http.OutgoingHttpHeaders = {}; + + headers["content-encoding"] = 'gzip'; + + utils.setContentLengthAttributes(headers, attributes, true); + + assert.strictEqual(attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH], undefined); + assert.strictEqual(attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], undefined); + assert.strictEqual(attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED], undefined); + assert.strictEqual(attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], undefined); + }); + }); }); diff --git a/packages/opentelemetry-plugin-http/test/utils/assertSpan.ts b/packages/opentelemetry-plugin-http/test/utils/assertSpan.ts index f9bda54dd0..e92dcd7020 100644 --- a/packages/opentelemetry-plugin-http/test/utils/assertSpan.ts +++ b/packages/opentelemetry-plugin-http/test/utils/assertSpan.ts @@ -83,7 +83,24 @@ export const assertSpan = ( ); } } + if (span.kind === SpanKind.CLIENT) { + if (validations.resHeaders['content-length']) { + const contentLength = validations.resHeaders['content-length'] + + if (validations.resHeaders['content-encoding'] && validations.resHeaders['content-encoding'] != 'identity') { + assert.strictEqual( + span.attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], + contentLength + ); + } else { + assert.strictEqual( + span.attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED], + contentLength + ); + } + } + assert.strictEqual( span.attributes[GeneralAttribute.NET_PEER_NAME], validations.hostname, @@ -105,6 +122,22 @@ export const assertSpan = ( ); } if (span.kind === SpanKind.SERVER) { + if (validations.reqHeaders && validations.reqHeaders['content-length']) { + const contentLength = validations.reqHeaders['content-length'] + + if (validations.reqHeaders['content-encoding'] && validations.reqHeaders['content-encoding'] != 'identity') { + assert.strictEqual( + span.attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH], + contentLength + ); + } else { + assert.strictEqual( + span.attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], + contentLength + ); + } + } + if (validations.serverName) { assert.strictEqual( span.attributes[HttpAttribute.HTTP_SERVER_NAME], diff --git a/packages/opentelemetry-semantic-conventions/src/trace/http.ts b/packages/opentelemetry-semantic-conventions/src/trace/http.ts index ae8e7e657b..5193fd17ec 100644 --- a/packages/opentelemetry-semantic-conventions/src/trace/http.ts +++ b/packages/opentelemetry-semantic-conventions/src/trace/http.ts @@ -26,8 +26,8 @@ export const HttpAttribute = { HTTP_CLIENT_IP: 'http.client_ip', HTTP_SCHEME: 'http.scheme', HTTP_RESPONSE_CONTENT_LENGTH: 'http.response_content_length', - HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED: 'http.request_content_length_uncompressed', - HTTP_REQUEST_CONTENT_LENGTH: 'http.response_content_length', + HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED: 'http.response_content_length_uncompressed', + HTTP_REQUEST_CONTENT_LENGTH: 'http.request_content_length', HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED: 'http.request_content_length_uncompressed', // NOT ON OFFICIAL SPEC From baf1b68f2f54ae491776932121a6a9c71d1e2284 Mon Sep 17 00:00:00 2001 From: Nick Tzaperas Date: Mon, 26 Oct 2020 14:01:02 -0700 Subject: [PATCH 3/8] feat(opentelemetry-js): linting --- .../opentelemetry-plugin-http/src/utils.ts | 35 ++-- .../test/functionals/utils.test.ts | 183 +++++++++++++----- .../test/utils/assertSpan.ts | 22 ++- .../src/trace/http.ts | 6 +- 4 files changed, 176 insertions(+), 70 deletions(-) diff --git a/packages/opentelemetry-plugin-http/src/utils.ts b/packages/opentelemetry-plugin-http/src/utils.ts index a523909604..abcd4c165c 100644 --- a/packages/opentelemetry-plugin-http/src/utils.ts +++ b/packages/opentelemetry-plugin-http/src/utils.ts @@ -230,26 +230,32 @@ export const setContentLengthAttributes = ( ) => { let isCompressed = false; - if (headers['content-length'] === undefined) - return; + if (headers['content-length'] === undefined) return; - if (headers['content-encoding'] && headers['content-encoding'] !== 'identity') { - isCompressed = true - }; + if ( + headers['content-encoding'] && + headers['content-encoding'] !== 'identity' + ) { + isCompressed = true; + } if (isCompressed) { if (isRequest) { - attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH] = headers['content-length'] + attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH] = + headers['content-length']; } else { - attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH] = headers['content-length'] - }; + attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH] = + headers['content-length']; + } } else { if (isRequest) { - attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED] = headers['content-length'] + attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED] = + headers['content-length']; } else { - attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED] = headers['content-length'] - }; - }; + attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED] = + headers['content-length']; + } + } }; /** @@ -394,14 +400,13 @@ export const getOutgoingRequestAttributesOnResponse = ( const { statusCode, statusMessage, httpVersion, headers, socket } = response; const { remoteAddress, remotePort } = socket; - const attributes: Attributes = { [GeneralAttribute.NET_PEER_IP]: remoteAddress, [GeneralAttribute.NET_PEER_PORT]: remotePort, [HttpAttribute.HTTP_HOST]: `${options.hostname}:${remotePort}`, }; - setContentLengthAttributes(headers, attributes, false) + setContentLengthAttributes(headers, attributes, false); if (statusCode) { attributes[HttpAttribute.HTTP_STATUS_CODE] = statusCode; @@ -463,7 +468,7 @@ export const getIncomingRequestAttributes = ( attributes[HttpAttribute.HTTP_USER_AGENT] = userAgent; } - setContentLengthAttributes(headers, attributes, true) + setContentLengthAttributes(headers, attributes, true); const httpKindAttributes = getAttributesFromHttpKind(httpVersion); return Object.assign(attributes, httpKindAttributes); diff --git a/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts b/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts index 01f8744e38..95a1b61a33 100644 --- a/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts +++ b/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts @@ -13,7 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Attributes, CanonicalCode, SpanKind, TraceFlags } from '@opentelemetry/api'; +import { + Attributes, + CanonicalCode, + SpanKind, + TraceFlags, +} from '@opentelemetry/api'; import { NoopLogger } from '@opentelemetry/core'; import { BasicTracerProvider, Span } from '@opentelemetry/tracing'; import { HttpAttribute } from '@opentelemetry/semantic-conventions'; @@ -314,110 +319,194 @@ describe('Utility', () => { it('should set response content-length uncompressed attribute with no content-encoding header', () => { const attributes: Attributes = {}; - let headers: http.IncomingHttpHeaders = {}; + const headers: http.IncomingHttpHeaders = {}; - headers["content-length"] = '1200'; + headers['content-length'] = '1200'; utils.setContentLengthAttributes(headers, attributes, false); - assert.strictEqual(attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED], '1200'); - assert.strictEqual(attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], undefined); - assert.strictEqual(attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], undefined); - assert.strictEqual(attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH], undefined); + assert.strictEqual( + attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED], + '1200' + ); + assert.strictEqual( + attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], + undefined + ); + assert.strictEqual( + attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], + undefined + ); + assert.strictEqual( + attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH], + undefined + ); }); it('should set response content-length uncompressed attribute with "identity" content-encoding header', () => { const attributes: Attributes = {}; - let headers: http.IncomingHttpHeaders = {}; + const headers: http.IncomingHttpHeaders = {}; - headers["content-length"] = '1200'; - headers["content-encoding"] = 'identity'; + headers['content-length'] = '1200'; + headers['content-encoding'] = 'identity'; utils.setContentLengthAttributes(headers, attributes, false); - assert.strictEqual(attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED], '1200'); - assert.strictEqual(attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], undefined); - assert.strictEqual(attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], undefined); - assert.strictEqual(attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH], undefined); + assert.strictEqual( + attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED], + '1200' + ); + assert.strictEqual( + attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], + undefined + ); + assert.strictEqual( + attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], + undefined + ); + assert.strictEqual( + attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH], + undefined + ); }); it('should set response content-length compressed attribute with "gzip" content-encoding header', () => { const attributes: Attributes = {}; - let headers: http.IncomingHttpHeaders = {}; + const headers: http.IncomingHttpHeaders = {}; - headers["content-length"] = '1200'; - headers["content-encoding"] = 'gzip'; + headers['content-length'] = '1200'; + headers['content-encoding'] = 'gzip'; utils.setContentLengthAttributes(headers, attributes, false); - assert.strictEqual(attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], '1200'); - assert.strictEqual(attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED], undefined); - assert.strictEqual(attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], undefined); - assert.strictEqual(attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH], undefined); + assert.strictEqual( + attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], + '1200' + ); + assert.strictEqual( + attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED], + undefined + ); + assert.strictEqual( + attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], + undefined + ); + assert.strictEqual( + attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH], + undefined + ); }); it('should set request content-length uncompressed attribute with no content-encoding header', () => { const attributes: Attributes = {}; - let headers: http.OutgoingHttpHeaders = {}; + const headers: http.OutgoingHttpHeaders = {}; - headers["content-length"] = '1200'; + headers['content-length'] = '1200'; utils.setContentLengthAttributes(headers, attributes, true); - assert.strictEqual(attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], '1200'); - assert.strictEqual(attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH], undefined); - assert.strictEqual(attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED], undefined); - assert.strictEqual(attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], undefined); + assert.strictEqual( + attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], + '1200' + ); + assert.strictEqual( + attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH], + undefined + ); + assert.strictEqual( + attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED], + undefined + ); + assert.strictEqual( + attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], + undefined + ); }); it('should set request content-length uncompressed attribute with "identity" content-encoding header', () => { const attributes: Attributes = {}; - let headers: http.OutgoingHttpHeaders = {}; + const headers: http.OutgoingHttpHeaders = {}; - headers["content-length"] = '1200'; - headers["content-encoding"] = 'identity'; + headers['content-length'] = '1200'; + headers['content-encoding'] = 'identity'; utils.setContentLengthAttributes(headers, attributes, true); - assert.strictEqual(attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], '1200'); - assert.strictEqual(attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH], undefined); - assert.strictEqual(attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED], undefined); - assert.strictEqual(attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], undefined); + assert.strictEqual( + attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], + '1200' + ); + assert.strictEqual( + attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH], + undefined + ); + assert.strictEqual( + attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED], + undefined + ); + assert.strictEqual( + attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], + undefined + ); }); it('should set request content-length compressed attribute with "gzip" content-encoding header', () => { const attributes: Attributes = {}; - let headers: http.OutgoingHttpHeaders = {}; + const headers: http.OutgoingHttpHeaders = {}; - headers["content-length"] = '1200'; - headers["content-encoding"] = 'gzip'; + headers['content-length'] = '1200'; + headers['content-encoding'] = 'gzip'; utils.setContentLengthAttributes(headers, attributes, true); - assert.strictEqual(attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH], '1200'); - assert.strictEqual(attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], undefined); - assert.strictEqual(attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED], undefined); - assert.strictEqual(attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], undefined); + assert.strictEqual( + attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH], + '1200' + ); + assert.strictEqual( + attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], + undefined + ); + assert.strictEqual( + attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED], + undefined + ); + assert.strictEqual( + attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], + undefined + ); }); it('should set no attributes with no content-length header', () => { const attributes: Attributes = {}; - let headers: http.OutgoingHttpHeaders = {}; + const headers: http.OutgoingHttpHeaders = {}; - headers["content-encoding"] = 'gzip'; + headers['content-encoding'] = 'gzip'; utils.setContentLengthAttributes(headers, attributes, true); - assert.strictEqual(attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH], undefined); - assert.strictEqual(attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], undefined); - assert.strictEqual(attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED], undefined); - assert.strictEqual(attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], undefined); + assert.strictEqual( + attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH], + undefined + ); + assert.strictEqual( + attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], + undefined + ); + assert.strictEqual( + attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED], + undefined + ); + assert.strictEqual( + attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], + undefined + ); }); }); }); diff --git a/packages/opentelemetry-plugin-http/test/utils/assertSpan.ts b/packages/opentelemetry-plugin-http/test/utils/assertSpan.ts index e92dcd7020..1bf4381030 100644 --- a/packages/opentelemetry-plugin-http/test/utils/assertSpan.ts +++ b/packages/opentelemetry-plugin-http/test/utils/assertSpan.ts @@ -86,16 +86,21 @@ export const assertSpan = ( if (span.kind === SpanKind.CLIENT) { if (validations.resHeaders['content-length']) { - const contentLength = validations.resHeaders['content-length'] + const contentLength = validations.resHeaders['content-length']; - if (validations.resHeaders['content-encoding'] && validations.resHeaders['content-encoding'] != 'identity') { + if ( + validations.resHeaders['content-encoding'] && + validations.resHeaders['content-encoding'] != 'identity' + ) { assert.strictEqual( span.attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], contentLength ); } else { assert.strictEqual( - span.attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED], + span.attributes[ + HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED + ], contentLength ); } @@ -123,16 +128,21 @@ export const assertSpan = ( } if (span.kind === SpanKind.SERVER) { if (validations.reqHeaders && validations.reqHeaders['content-length']) { - const contentLength = validations.reqHeaders['content-length'] + const contentLength = validations.reqHeaders['content-length']; - if (validations.reqHeaders['content-encoding'] && validations.reqHeaders['content-encoding'] != 'identity') { + if ( + validations.reqHeaders['content-encoding'] && + validations.reqHeaders['content-encoding'] != 'identity' + ) { assert.strictEqual( span.attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH], contentLength ); } else { assert.strictEqual( - span.attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], + span.attributes[ + HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED + ], contentLength ); } diff --git a/packages/opentelemetry-semantic-conventions/src/trace/http.ts b/packages/opentelemetry-semantic-conventions/src/trace/http.ts index 5193fd17ec..04618c5fd9 100644 --- a/packages/opentelemetry-semantic-conventions/src/trace/http.ts +++ b/packages/opentelemetry-semantic-conventions/src/trace/http.ts @@ -26,9 +26,11 @@ export const HttpAttribute = { HTTP_CLIENT_IP: 'http.client_ip', HTTP_SCHEME: 'http.scheme', HTTP_RESPONSE_CONTENT_LENGTH: 'http.response_content_length', - HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED: 'http.response_content_length_uncompressed', + HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED: + 'http.response_content_length_uncompressed', HTTP_REQUEST_CONTENT_LENGTH: 'http.request_content_length', - HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED: 'http.request_content_length_uncompressed', + HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED: + 'http.request_content_length_uncompressed', // NOT ON OFFICIAL SPEC HTTP_ERROR_NAME: 'http.error_name', From 81c2bc9d623f7d7ffcb85f0914f31c736d9fa4f5 Mon Sep 17 00:00:00 2001 From: Nick Tzaperas Date: Wed, 28 Oct 2020 12:11:35 -0700 Subject: [PATCH 4/8] feat(opentelemetry-js): verify content length attributes are a number --- .../opentelemetry-plugin-http/src/utils.ts | 15 +- .../test/functionals/utils.test.ts | 175 +++++------------- .../test/utils/assertSpan.ts | 2 +- 3 files changed, 56 insertions(+), 136 deletions(-) diff --git a/packages/opentelemetry-plugin-http/src/utils.ts b/packages/opentelemetry-plugin-http/src/utils.ts index abcd4c165c..9433582a42 100644 --- a/packages/opentelemetry-plugin-http/src/utils.ts +++ b/packages/opentelemetry-plugin-http/src/utils.ts @@ -239,23 +239,22 @@ export const setContentLengthAttributes = ( isCompressed = true; } + let key = null; if (isCompressed) { if (isRequest) { - attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH] = - headers['content-length']; + key = HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH; } else { - attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH] = - headers['content-length']; + key = HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH; } } else { if (isRequest) { - attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED] = - headers['content-length']; + key = HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED; } else { - attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED] = - headers['content-length']; + key = HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED; } } + + attributes[key] = Number(headers['content-length']); }; /** diff --git a/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts b/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts index 95a1b61a33..bfe6ffd9b4 100644 --- a/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts +++ b/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts @@ -316,197 +316,118 @@ describe('Utility', () => { }); describe('setContentLengthAttributes', () => { + // Verify the key in the given attributes is set to the given value, + // and that no other HTTP Content Length attributes are set. + function verifyValueInAttributes( + attributes: Attributes, + key: string | undefined, + value: number + ) { + const httpAttributes = [ + HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED, + HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH, + HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED, + HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH, + ]; + + for (const attr of httpAttributes) { + if (attr === key) { + assert.strictEqual(attributes[attr], value); + } else { + assert.strictEqual(attributes[attr], undefined); + } + } + } + it('should set response content-length uncompressed attribute with no content-encoding header', () => { const attributes: Attributes = {}; - const headers: http.IncomingHttpHeaders = {}; - headers['content-length'] = '1200'; - utils.setContentLengthAttributes(headers, attributes, false); - assert.strictEqual( - attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED], - '1200' - ); - assert.strictEqual( - attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], - undefined - ); - assert.strictEqual( - attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], - undefined - ); - assert.strictEqual( - attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH], - undefined + verifyValueInAttributes( + attributes, + HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED, + 1200 ); }); it('should set response content-length uncompressed attribute with "identity" content-encoding header', () => { const attributes: Attributes = {}; - const headers: http.IncomingHttpHeaders = {}; - headers['content-length'] = '1200'; headers['content-encoding'] = 'identity'; - utils.setContentLengthAttributes(headers, attributes, false); - assert.strictEqual( - attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED], - '1200' - ); - assert.strictEqual( - attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], - undefined - ); - assert.strictEqual( - attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], - undefined - ); - assert.strictEqual( - attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH], - undefined + verifyValueInAttributes( + attributes, + HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED, + 1200 ); }); it('should set response content-length compressed attribute with "gzip" content-encoding header', () => { const attributes: Attributes = {}; - const headers: http.IncomingHttpHeaders = {}; - headers['content-length'] = '1200'; headers['content-encoding'] = 'gzip'; - utils.setContentLengthAttributes(headers, attributes, false); - assert.strictEqual( - attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], - '1200' - ); - assert.strictEqual( - attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED], - undefined - ); - assert.strictEqual( - attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], - undefined - ); - assert.strictEqual( - attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH], - undefined + verifyValueInAttributes( + attributes, + HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH, + 1200 ); }); it('should set request content-length uncompressed attribute with no content-encoding header', () => { const attributes: Attributes = {}; - const headers: http.OutgoingHttpHeaders = {}; - headers['content-length'] = '1200'; - utils.setContentLengthAttributes(headers, attributes, true); - assert.strictEqual( - attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], - '1200' - ); - assert.strictEqual( - attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH], - undefined - ); - assert.strictEqual( - attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED], - undefined - ); - assert.strictEqual( - attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], - undefined + verifyValueInAttributes( + attributes, + HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED, + 1200 ); }); it('should set request content-length uncompressed attribute with "identity" content-encoding header', () => { const attributes: Attributes = {}; - const headers: http.OutgoingHttpHeaders = {}; - headers['content-length'] = '1200'; headers['content-encoding'] = 'identity'; - utils.setContentLengthAttributes(headers, attributes, true); - assert.strictEqual( - attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], - '1200' - ); - assert.strictEqual( - attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH], - undefined - ); - assert.strictEqual( - attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED], - undefined - ); - assert.strictEqual( - attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], - undefined + verifyValueInAttributes( + attributes, + HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED, + 1200 ); }); it('should set request content-length compressed attribute with "gzip" content-encoding header', () => { const attributes: Attributes = {}; - const headers: http.OutgoingHttpHeaders = {}; - headers['content-length'] = '1200'; headers['content-encoding'] = 'gzip'; - utils.setContentLengthAttributes(headers, attributes, true); - assert.strictEqual( - attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH], - '1200' - ); - assert.strictEqual( - attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], - undefined - ); - assert.strictEqual( - attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED], - undefined - ); - assert.strictEqual( - attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], - undefined + verifyValueInAttributes( + attributes, + HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH, + 1200 ); }); it('should set no attributes with no content-length header', () => { const attributes: Attributes = {}; - const headers: http.OutgoingHttpHeaders = {}; - headers['content-encoding'] = 'gzip'; - utils.setContentLengthAttributes(headers, attributes, true); - assert.strictEqual( - attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH], - undefined - ); - assert.strictEqual( - attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED], - undefined - ); - assert.strictEqual( - attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED], - undefined - ); - assert.strictEqual( - attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], - undefined - ); + verifyValueInAttributes(attributes, undefined, 1200); }); }); }); diff --git a/packages/opentelemetry-plugin-http/test/utils/assertSpan.ts b/packages/opentelemetry-plugin-http/test/utils/assertSpan.ts index 1bf4381030..ea3bdcb77e 100644 --- a/packages/opentelemetry-plugin-http/test/utils/assertSpan.ts +++ b/packages/opentelemetry-plugin-http/test/utils/assertSpan.ts @@ -86,7 +86,7 @@ export const assertSpan = ( if (span.kind === SpanKind.CLIENT) { if (validations.resHeaders['content-length']) { - const contentLength = validations.resHeaders['content-length']; + const contentLength = Number(validations.resHeaders['content-length']); if ( validations.resHeaders['content-encoding'] && From e661fff40f0e2944930bc77c12291543bfafd751 Mon Sep 17 00:00:00 2001 From: Carlo Pearson Date: Thu, 5 Nov 2020 15:04:52 -0800 Subject: [PATCH 5/8] feat(opentelemetry-js): refactor setting content-length attributes --- .../opentelemetry-plugin-http/src/utils.ts | 72 +++++---- .../test/functionals/utils.test.ts | 142 +++++++++++------- 2 files changed, 127 insertions(+), 87 deletions(-) diff --git a/packages/opentelemetry-plugin-http/src/utils.ts b/packages/opentelemetry-plugin-http/src/utils.ts index 9433582a42..e0b4494f1a 100644 --- a/packages/opentelemetry-plugin-http/src/utils.ts +++ b/packages/opentelemetry-plugin-http/src/utils.ts @@ -221,42 +221,56 @@ export const setSpanWithError = ( * Adds attributes for content-length and content-encoding HTTP headers * @param { OutgoingHttpHeaders | IncomingHttpHeaders } headers http headers * @param { Attributes } Attributes span attributes - * @param { boolean } isRequest set true for setting request content-header */ -export const setContentLengthAttributes = ( - headers: OutgoingHttpHeaders | IncomingHttpHeaders, +export const setRequestContentLengthAttribute = ( + request: IncomingMessage, attributes: Attributes, - isRequest: boolean ) => { - let isCompressed = false; - - if (headers['content-length'] === undefined) return; + const length = getContentLength(request.headers); + if (length === null) return; - if ( - headers['content-encoding'] && - headers['content-encoding'] !== 'identity' - ) { - isCompressed = true; + if (isCompressed(request.headers)) { + attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH] = length; + } else { + attributes[HttpAttribute. HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED] = length; } +} - let key = null; - if (isCompressed) { - if (isRequest) { - key = HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH; - } else { - key = HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH; - } +/** + * Adds attributes for content-length and content-encoding HTTP headers + * @param { OutgoingHttpHeaders | IncomingHttpHeaders } headers http headers + * @param { Attributes } Attributes span attributes + */ +export const setResponseContentLengthAttribute = ( + response: IncomingMessage, + attributes: Attributes, +) => { + const length = getContentLength(response.headers); + if (length === null) return; + + if (isCompressed(response.headers)) { + attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH] = length; } else { - if (isRequest) { - key = HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED; - } else { - key = HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED; - } + attributes[HttpAttribute. HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED] = length; } - - attributes[key] = Number(headers['content-length']); }; +function getContentLength(headers: OutgoingHttpHeaders | IncomingHttpHeaders) : number | null { + const contentLengthHeader = headers['content-length']; + if (contentLengthHeader === undefined) return null; + + const contentLength = parseInt(contentLengthHeader as string, 10); + if (isNaN(contentLength)) return null; + + return contentLength; +} + +export const isCompressed = (headers: OutgoingHttpHeaders | IncomingHttpHeaders) : Boolean => { + const encoding = headers['content-encoding']; + + return !!encoding && encoding != 'identity' +} + /** * Makes sure options is an url object * return an object with default value and parsed options @@ -396,7 +410,7 @@ export const getOutgoingRequestAttributesOnResponse = ( response: IncomingMessage, options: { hostname: string } ): Attributes => { - const { statusCode, statusMessage, httpVersion, headers, socket } = response; + const { statusCode, statusMessage, httpVersion, socket } = response; const { remoteAddress, remotePort } = socket; const attributes: Attributes = { @@ -405,7 +419,7 @@ export const getOutgoingRequestAttributesOnResponse = ( [HttpAttribute.HTTP_HOST]: `${options.hostname}:${remotePort}`, }; - setContentLengthAttributes(headers, attributes, false); + setResponseContentLengthAttribute(response, attributes); if (statusCode) { attributes[HttpAttribute.HTTP_STATUS_CODE] = statusCode; @@ -467,7 +481,7 @@ export const getIncomingRequestAttributes = ( attributes[HttpAttribute.HTTP_USER_AGENT] = userAgent; } - setContentLengthAttributes(headers, attributes, true); + setRequestContentLengthAttribute(request, attributes); const httpKindAttributes = getAttributesFromHttpKind(httpVersion); return Object.assign(attributes, httpKindAttributes); diff --git a/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts b/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts index bfe6ffd9b4..8597d1b7a2 100644 --- a/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts +++ b/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts @@ -315,117 +315,143 @@ describe('Utility', () => { }); }); - describe('setContentLengthAttributes', () => { - // Verify the key in the given attributes is set to the given value, - // and that no other HTTP Content Length attributes are set. - function verifyValueInAttributes( - attributes: Attributes, - key: string | undefined, - value: number - ) { - const httpAttributes = [ - HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED, - HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH, - HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED, - HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH, - ]; - - for (const attr of httpAttributes) { - if (attr === key) { - assert.strictEqual(attributes[attr], value); - } else { - assert.strictEqual(attributes[attr], undefined); - } + // Verify the key in the given attributes is set to the given value, + // and that no other HTTP Content Length attributes are set. + function verifyValueInAttributes( + attributes: Attributes, + key: string | undefined, + value: number + ) { + const httpAttributes = [ + HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED, + HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH, + HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED, + HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH, + ]; + + for (const attr of httpAttributes) { + if (attr === key) { + assert.strictEqual(attributes[attr], value); + } else { + assert.strictEqual(attributes[attr], undefined); } } + } - it('should set response content-length uncompressed attribute with no content-encoding header', () => { + describe('setRequestContentLengthAttributes()', () => { + it('should set request content-length uncompressed attribute with no content-encoding header', () => { const attributes: Attributes = {}; - const headers: http.IncomingHttpHeaders = {}; - headers['content-length'] = '1200'; - utils.setContentLengthAttributes(headers, attributes, false); + const request = {} as IncomingMessage; + + request.headers = { + 'content-length': '1200' + }; + utils.setRequestContentLengthAttribute(request, attributes); verifyValueInAttributes( attributes, - HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED, + HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED, 1200 ); }); - it('should set response content-length uncompressed attribute with "identity" content-encoding header', () => { + it('should set request content-length uncompressed attribute with "identity" content-encoding header', () => { const attributes: Attributes = {}; - const headers: http.IncomingHttpHeaders = {}; - headers['content-length'] = '1200'; - headers['content-encoding'] = 'identity'; - utils.setContentLengthAttributes(headers, attributes, false); + const request = {} as IncomingMessage; + request.headers = { + 'content-length': '1200', + 'content-encoding': 'identity' + }; + utils.setRequestContentLengthAttribute(request, attributes); verifyValueInAttributes( attributes, - HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED, + HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED, 1200 ); }); - it('should set response content-length compressed attribute with "gzip" content-encoding header', () => { + it('should set request content-length compressed attribute with "gzip" content-encoding header', () => { const attributes: Attributes = {}; - const headers: http.IncomingHttpHeaders = {}; - headers['content-length'] = '1200'; - headers['content-encoding'] = 'gzip'; - utils.setContentLengthAttributes(headers, attributes, false); + const request = {} as IncomingMessage; + request.headers = { + "content-length": "1200", + "content-encoding": "gzip" + }; + utils.setRequestContentLengthAttribute(request, attributes); verifyValueInAttributes( attributes, - HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH, + HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH, 1200 ); }); + }) - it('should set request content-length uncompressed attribute with no content-encoding header', () => { + describe('setResponseContentLengthAttributes()', () => { + it('should set response content-length uncompressed attribute with no content-encoding header', () => { const attributes: Attributes = {}; - const headers: http.OutgoingHttpHeaders = {}; - headers['content-length'] = '1200'; - utils.setContentLengthAttributes(headers, attributes, true); + + const response = {} as IncomingMessage; + + response.headers = { + "content-length": "1200" + }; + utils.setResponseContentLengthAttribute(response, attributes); verifyValueInAttributes( attributes, - HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED, + HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED, 1200 ); }); - it('should set request content-length uncompressed attribute with "identity" content-encoding header', () => { + it('should set response content-length uncompressed attribute with "identity" content-encoding header', () => { const attributes: Attributes = {}; - const headers: http.OutgoingHttpHeaders = {}; - headers['content-length'] = '1200'; - headers['content-encoding'] = 'identity'; - utils.setContentLengthAttributes(headers, attributes, true); + + const response = {} as IncomingMessage; + + response.headers = { + "content-length": "1200", + "content-encoding": "identity" + }; + + utils.setResponseContentLengthAttribute(response, attributes); verifyValueInAttributes( attributes, - HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED, + HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED, 1200 ); }); - it('should set request content-length compressed attribute with "gzip" content-encoding header', () => { + it('should set response content-length compressed attribute with "gzip" content-encoding header', () => { const attributes: Attributes = {}; - const headers: http.OutgoingHttpHeaders = {}; - headers['content-length'] = '1200'; - headers['content-encoding'] = 'gzip'; - utils.setContentLengthAttributes(headers, attributes, true); + + const response = {} as IncomingMessage; + + response.headers = { + "content-length": "1200", + "content-encoding": "gzip" + }; + + utils.setResponseContentLengthAttribute(response, attributes); verifyValueInAttributes( attributes, - HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH, + HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH, 1200 ); }); it('should set no attributes with no content-length header', () => { const attributes: Attributes = {}; - const headers: http.OutgoingHttpHeaders = {}; - headers['content-encoding'] = 'gzip'; - utils.setContentLengthAttributes(headers, attributes, true); + const message = {} as IncomingMessage; + + message.headers = { + "content-encoding": "gzip" + }; + utils.setResponseContentLengthAttribute(message, attributes); verifyValueInAttributes(attributes, undefined, 1200); }); From e591bb7b15b45cd7013a441ec9cfc927e7f24ee1 Mon Sep 17 00:00:00 2001 From: Carlo Pearson Date: Thu, 5 Nov 2020 15:50:44 -0800 Subject: [PATCH 6/8] feat(opentelemetry-js): lint fixes --- packages/opentelemetry-plugin-http/src/utils.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/opentelemetry-plugin-http/src/utils.ts b/packages/opentelemetry-plugin-http/src/utils.ts index e0b4494f1a..931032cb54 100644 --- a/packages/opentelemetry-plugin-http/src/utils.ts +++ b/packages/opentelemetry-plugin-http/src/utils.ts @@ -224,7 +224,7 @@ export const setSpanWithError = ( */ export const setRequestContentLengthAttribute = ( request: IncomingMessage, - attributes: Attributes, + attributes: Attributes ) => { const length = getContentLength(request.headers); if (length === null) return; @@ -232,9 +232,9 @@ export const setRequestContentLengthAttribute = ( if (isCompressed(request.headers)) { attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH] = length; } else { - attributes[HttpAttribute. HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED] = length; + attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH_UNCOMPRESSED] = length; } -} +}; /** * Adds attributes for content-length and content-encoding HTTP headers @@ -243,7 +243,7 @@ export const setRequestContentLengthAttribute = ( */ export const setResponseContentLengthAttribute = ( response: IncomingMessage, - attributes: Attributes, + attributes: Attributes ) => { const length = getContentLength(response.headers); if (length === null) return; @@ -251,7 +251,7 @@ export const setResponseContentLengthAttribute = ( if (isCompressed(response.headers)) { attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH] = length; } else { - attributes[HttpAttribute. HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED] = length; + attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED] = length; } }; From 5b5ede927dd8e6cc32b67b8f0d4b8e13d9c08922 Mon Sep 17 00:00:00 2001 From: Carlo Pearson Date: Thu, 5 Nov 2020 15:58:10 -0800 Subject: [PATCH 7/8] feat(opentelemetry-js): lint fixes --- .../opentelemetry-plugin-http/src/utils.ts | 16 +++++++---- .../test/functionals/utils.test.ts | 28 +++++++++---------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/packages/opentelemetry-plugin-http/src/utils.ts b/packages/opentelemetry-plugin-http/src/utils.ts index 931032cb54..cfc44f3b93 100644 --- a/packages/opentelemetry-plugin-http/src/utils.ts +++ b/packages/opentelemetry-plugin-http/src/utils.ts @@ -251,11 +251,15 @@ export const setResponseContentLengthAttribute = ( if (isCompressed(response.headers)) { attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH] = length; } else { - attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED] = length; + attributes[ + HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH_UNCOMPRESSED + ] = length; } }; -function getContentLength(headers: OutgoingHttpHeaders | IncomingHttpHeaders) : number | null { +function getContentLength( + headers: OutgoingHttpHeaders | IncomingHttpHeaders +): number | null { const contentLengthHeader = headers['content-length']; if (contentLengthHeader === undefined) return null; @@ -265,11 +269,13 @@ function getContentLength(headers: OutgoingHttpHeaders | IncomingHttpHeaders) : return contentLength; } -export const isCompressed = (headers: OutgoingHttpHeaders | IncomingHttpHeaders) : Boolean => { +export const isCompressed = ( + headers: OutgoingHttpHeaders | IncomingHttpHeaders +): boolean => { const encoding = headers['content-encoding']; - return !!encoding && encoding != 'identity' -} + return !!encoding && encoding != 'identity'; +}; /** * Makes sure options is an url object diff --git a/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts b/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts index 8597d1b7a2..219b9c2882 100644 --- a/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts +++ b/packages/opentelemetry-plugin-http/test/functionals/utils.test.ts @@ -344,7 +344,7 @@ describe('Utility', () => { const request = {} as IncomingMessage; request.headers = { - 'content-length': '1200' + 'content-length': '1200', }; utils.setRequestContentLengthAttribute(request, attributes); @@ -360,7 +360,7 @@ describe('Utility', () => { const request = {} as IncomingMessage; request.headers = { 'content-length': '1200', - 'content-encoding': 'identity' + 'content-encoding': 'identity', }; utils.setRequestContentLengthAttribute(request, attributes); @@ -375,8 +375,8 @@ describe('Utility', () => { const attributes: Attributes = {}; const request = {} as IncomingMessage; request.headers = { - "content-length": "1200", - "content-encoding": "gzip" + 'content-length': '1200', + 'content-encoding': 'gzip', }; utils.setRequestContentLengthAttribute(request, attributes); @@ -386,16 +386,16 @@ describe('Utility', () => { 1200 ); }); - }) + }); describe('setResponseContentLengthAttributes()', () => { it('should set response content-length uncompressed attribute with no content-encoding header', () => { const attributes: Attributes = {}; const response = {} as IncomingMessage; - + response.headers = { - "content-length": "1200" + 'content-length': '1200', }; utils.setResponseContentLengthAttribute(response, attributes); @@ -408,12 +408,12 @@ describe('Utility', () => { it('should set response content-length uncompressed attribute with "identity" content-encoding header', () => { const attributes: Attributes = {}; - + const response = {} as IncomingMessage; response.headers = { - "content-length": "1200", - "content-encoding": "identity" + 'content-length': '1200', + 'content-encoding': 'identity', }; utils.setResponseContentLengthAttribute(response, attributes); @@ -427,12 +427,12 @@ describe('Utility', () => { it('should set response content-length compressed attribute with "gzip" content-encoding header', () => { const attributes: Attributes = {}; - + const response = {} as IncomingMessage; response.headers = { - "content-length": "1200", - "content-encoding": "gzip" + 'content-length': '1200', + 'content-encoding': 'gzip', }; utils.setResponseContentLengthAttribute(response, attributes); @@ -449,7 +449,7 @@ describe('Utility', () => { const message = {} as IncomingMessage; message.headers = { - "content-encoding": "gzip" + 'content-encoding': 'gzip', }; utils.setResponseContentLengthAttribute(message, attributes); From 453fff6ee1638faea1bf026b5394eb5a64573e31 Mon Sep 17 00:00:00 2001 From: Nick Tzaperas Date: Wed, 9 Dec 2020 09:20:59 -0800 Subject: [PATCH 8/8] fix: incorrect docs --- packages/opentelemetry-plugin-http/src/utils.ts | 14 +++++++------- .../test/utils/assertSpan.ts | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/opentelemetry-plugin-http/src/utils.ts b/packages/opentelemetry-plugin-http/src/utils.ts index 8acb64b868..45d460891c 100644 --- a/packages/opentelemetry-plugin-http/src/utils.ts +++ b/packages/opentelemetry-plugin-http/src/utils.ts @@ -178,9 +178,9 @@ export const setSpanWithError = ( }; /** - * Adds attributes for content-length and content-encoding HTTP headers - * @param { OutgoingHttpHeaders | IncomingHttpHeaders } headers http headers - * @param { Attributes } Attributes span attributes + * Adds attributes for request content-length and content-encoding HTTP headers + * @param { IncomingMessage } Request object whose headers will be analyzed + * @param { Attributes } Attributes object to be modified */ export const setRequestContentLengthAttribute = ( request: IncomingMessage, @@ -197,9 +197,9 @@ export const setRequestContentLengthAttribute = ( }; /** - * Adds attributes for content-length and content-encoding HTTP headers - * @param { OutgoingHttpHeaders | IncomingHttpHeaders } headers http headers - * @param { Attributes } Attributes span attributes + * Adds attributes for response content-length and content-encoding HTTP headers + * @param { IncomingMessage } Response object whose headers will be analyzed + * @param { Attributes } Attributes object to be modified */ export const setResponseContentLengthAttribute = ( response: IncomingMessage, @@ -234,7 +234,7 @@ export const isCompressed = ( ): boolean => { const encoding = headers['content-encoding']; - return !!encoding && encoding != 'identity'; + return !!encoding && encoding !== 'identity'; }; /** diff --git a/packages/opentelemetry-plugin-http/test/utils/assertSpan.ts b/packages/opentelemetry-plugin-http/test/utils/assertSpan.ts index ea3bdcb77e..b7085f6039 100644 --- a/packages/opentelemetry-plugin-http/test/utils/assertSpan.ts +++ b/packages/opentelemetry-plugin-http/test/utils/assertSpan.ts @@ -90,7 +90,7 @@ export const assertSpan = ( if ( validations.resHeaders['content-encoding'] && - validations.resHeaders['content-encoding'] != 'identity' + validations.resHeaders['content-encoding'] !== 'identity' ) { assert.strictEqual( span.attributes[HttpAttribute.HTTP_RESPONSE_CONTENT_LENGTH], @@ -132,7 +132,7 @@ export const assertSpan = ( if ( validations.reqHeaders['content-encoding'] && - validations.reqHeaders['content-encoding'] != 'identity' + validations.reqHeaders['content-encoding'] !== 'identity' ) { assert.strictEqual( span.attributes[HttpAttribute.HTTP_REQUEST_CONTENT_LENGTH],