Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adding possibility of recording exception #1372

Merged
merged 16 commits into from
Aug 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions packages/opentelemetry-api/src/common/Exception.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright The OpenTelemetry 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
*
* https://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.
*/

interface ExceptionWithCode {
code: string;
name?: string;
message?: string;
stack?: string;
}

interface ExceptionWithMessage {
code?: string;
message: string;
name?: string;
stack?: string;
}

interface ExceptionWithName {
code?: string;
message?: string;
name: string;
stack?: string;
}

/**
* Defines Exception.
*
* string or an object with one of (message or name or code) and optional stack
*/
export type Exception =
| ExceptionWithCode
| ExceptionWithMessage
| ExceptionWithName
| string;
1 change: 1 addition & 0 deletions packages/opentelemetry-api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* limitations under the License.
*/

export * from './common/Exception';
export * from './common/Logger';
export * from './common/Time';
export * from './context/propagation/getter';
Expand Down
4 changes: 4 additions & 0 deletions packages/opentelemetry-api/src/trace/NoopSpan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* limitations under the License.
*/

import { Exception } from '../common/Exception';
import { TimeInput } from '../common/Time';
import { Attributes } from './attributes';
import { Span } from './span';
Expand Down Expand Up @@ -76,6 +77,9 @@ export class NoopSpan implements Span {
isRecording(): boolean {
return false;
}

// By default does nothing
recordException(exception: Exception, time?: TimeInput): void {}
}

export const NOOP_SPAN = new NoopSpan();
9 changes: 9 additions & 0 deletions packages/opentelemetry-api/src/trace/span.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* limitations under the License.
*/

import { Exception } from '../common/Exception';
import { Attributes } from './attributes';
import { SpanContext } from './span_context';
import { Status } from './status';
Expand Down Expand Up @@ -114,4 +115,12 @@ export interface Span {
* with the `AddEvent` operation and attributes using `setAttributes`.
*/
isRecording(): boolean;

/**
* Sets exception as a span event
* @param exception the exception the only accepted values are string or Error
* @param [time] the time to set as Span's event time. If not provided,
* use the current time.
*/
recordException(exception: Exception, time?: TimeInput): void;
obecny marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,17 @@ const mockResError = {
statusCode: 400,
};

// send is lazy loading file so need to wait a bit
const waitTimeMS = 20;

describe('CollectorMetricExporter - node with proto over http', () => {
let collectorExporter: CollectorMetricExporter;
let collectorExporterConfig: collectorTypes.CollectorExporterConfigBase;
let spyRequest: sinon.SinonSpy;
let spyWrite: sinon.SinonSpy;
let metrics: MetricRecord[];
describe('export', () => {
beforeEach(done => {
beforeEach(() => {
spyRequest = sinon.stub(http, 'request').returns(fakeRequest as any);
spyWrite = sinon.stub(fakeRequest, 'write');
collectorExporterConfig = {
Expand Down Expand Up @@ -86,11 +89,6 @@ describe('CollectorMetricExporter - node with proto over http', () => {
metrics[2].aggregator.update(7);
metrics[2].aggregator.update(14);
metrics[3].aggregator.update(5);

// due to lazy loading ensure to wait to next tick
setImmediate(() => {
done();
});
});
afterEach(() => {
spyRequest.restore();
Expand All @@ -108,7 +106,7 @@ describe('CollectorMetricExporter - node with proto over http', () => {
assert.strictEqual(options.method, 'POST');
assert.strictEqual(options.path, '/');
done();
});
}, waitTimeMS);
});

it('should set custom headers', done => {
Expand All @@ -119,7 +117,7 @@ describe('CollectorMetricExporter - node with proto over http', () => {
const options = args[0];
assert.strictEqual(options.headers['foo'], 'bar');
done();
});
}, waitTimeMS);
});

it('should successfully send metrics', done => {
Expand Down Expand Up @@ -154,7 +152,7 @@ describe('CollectorMetricExporter - node with proto over http', () => {
ensureExportMetricsServiceRequestIsSet(json);

done();
});
}, waitTimeMS);
});

it('should log the successful message', done => {
Expand All @@ -175,7 +173,7 @@ describe('CollectorMetricExporter - node with proto over http', () => {
assert.strictEqual(responseSpy.args[0][0], 0);
done();
});
});
}, waitTimeMS);
});

it('should log the error message', done => {
Expand All @@ -195,7 +193,7 @@ describe('CollectorMetricExporter - node with proto over http', () => {
assert.strictEqual(responseSpy.args[0][0], 1);
done();
});
});
}, waitTimeMS);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,17 @@ const mockResError = {
statusCode: 400,
};

// send is lazy loading file so need to wait a bit
const waitTimeMS = 20;

describe('CollectorExporter - node with proto over http', () => {
let collectorExporter: CollectorTraceExporter;
let collectorExporterConfig: collectorTypes.CollectorExporterConfigBase;
let spyRequest: sinon.SinonSpy;
let spyWrite: sinon.SinonSpy;
let spans: ReadableSpan[];
describe('export', () => {
beforeEach(done => {
beforeEach(() => {
spyRequest = sinon.stub(http, 'request').returns(fakeRequest as any);
spyWrite = sinon.stub(fakeRequest, 'write');
collectorExporterConfig = {
Expand All @@ -67,11 +70,6 @@ describe('CollectorExporter - node with proto over http', () => {
collectorExporter = new CollectorTraceExporter(collectorExporterConfig);
spans = [];
spans.push(Object.assign({}, mockedReadableSpan));

// due to lazy loading ensure to wait to next tick
setImmediate(() => {
done();
});
});
afterEach(() => {
spyRequest.restore();
Expand All @@ -89,7 +87,7 @@ describe('CollectorExporter - node with proto over http', () => {
assert.strictEqual(options.method, 'POST');
assert.strictEqual(options.path, '/');
done();
});
}, waitTimeMS);
});

it('should set custom headers', done => {
Expand All @@ -100,7 +98,7 @@ describe('CollectorExporter - node with proto over http', () => {
const options = args[0];
assert.strictEqual(options.headers['foo'], 'bar');
done();
});
}, waitTimeMS);
});

it('should successfully send the spans', done => {
Expand All @@ -121,7 +119,7 @@ describe('CollectorExporter - node with proto over http', () => {
ensureExportTraceServiceRequestIsSet(json);

done();
});
}, waitTimeMS);
});

it('should log the successful message', done => {
Expand All @@ -142,7 +140,7 @@ describe('CollectorExporter - node with proto over http', () => {
assert.strictEqual(responseSpy.args[0][0], 0);
done();
});
});
}, waitTimeMS);
});

it('should log the error message', done => {
Expand All @@ -162,7 +160,7 @@ describe('CollectorExporter - node with proto over http', () => {
assert.strictEqual(responseSpy.args[0][0], 1);
done();
});
});
}, waitTimeMS);
});
});
});
3 changes: 2 additions & 1 deletion packages/opentelemetry-plugin-http/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"precompile": "tsc --version",
"version:update": "node ../../scripts/version-update.js",
"compile": "npm run version:update && tsc -p .",
"prepare": "npm run compile"
"prepare": "npm run compile",
"watch": "tsc -w"
},
"keywords": [
"opentelemetry",
Expand Down
3 changes: 2 additions & 1 deletion packages/opentelemetry-semantic-conventions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"precompile": "tsc --version",
"version:update": "node ../../scripts/version-update.js",
"compile": "npm run version:update && tsc -p .",
"prepare": "npm run compile"
"prepare": "npm run compile",
"watch": "tsc -w"
},
"keywords": [
"opentelemetry",
Expand Down
23 changes: 23 additions & 0 deletions packages/opentelemetry-semantic-conventions/src/trace/exception.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright The OpenTelemetry 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
*
* https://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.
*/

export const ExceptionAttribute = {
MESSAGE: 'exception.message',
STACKTRACE: 'exception.stacktrace',
TYPE: 'exception.type',
};

export const ExceptionEventName = 'exception';
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@
* limitations under the License.
*/

export * from './database';
export * from './exception';
export * from './general';
export * from './rpc';
export * from './http';
export * from './database';
export * from './os';
export * from './rpc';
3 changes: 2 additions & 1 deletion packages/opentelemetry-tracing/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"@opentelemetry/api": "^0.10.2",
"@opentelemetry/context-base": "^0.10.2",
"@opentelemetry/core": "^0.10.2",
"@opentelemetry/resources": "^0.10.2"
"@opentelemetry/resources": "^0.10.2",
"@opentelemetry/semantic-conventions": "^0.10.2"
}
}
33 changes: 33 additions & 0 deletions packages/opentelemetry-tracing/src/Span.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ import {
timeInputToHrTime,
} from '@opentelemetry/core';
import { Resource } from '@opentelemetry/resources';
import {
ExceptionAttribute,
ExceptionEventName,
} from '@opentelemetry/semantic-conventions';
import { ReadableSpan } from './export/ReadableSpan';
import { Tracer } from './Tracer';
import { SpanProcessor } from './SpanProcessor';
Expand Down Expand Up @@ -178,6 +182,35 @@ export class Span implements api.Span, ReadableSpan {
return true;
}

recordException(exception: api.Exception, time: api.TimeInput = hrTime()) {
const attributes: api.Attributes = {};
if (typeof exception === 'string') {
attributes[ExceptionAttribute.MESSAGE] = exception;
} else if (exception) {
if (exception.code) {
attributes[ExceptionAttribute.TYPE] = exception.code;
} else if (exception.name) {
attributes[ExceptionAttribute.TYPE] = exception.name;
}
if (exception.message) {
attributes[ExceptionAttribute.MESSAGE] = exception.message;
}
if (exception.stack) {
attributes[ExceptionAttribute.STACKTRACE] = exception.stack;
}
}

// these are minimum requirements from spec
if (
attributes[ExceptionAttribute.TYPE] ||
attributes[ExceptionAttribute.MESSAGE]
) {
this.addEvent(ExceptionEventName, attributes as api.Attributes, time);
} else {
this._logger.warn(`Failed to record an exception ${exception}`);
}
obecny marked this conversation as resolved.
Show resolved Hide resolved
}

get duration(): api.HrTime {
return this._duration;
}
Expand Down
Loading