Skip to content

Commit 57f6ac4

Browse files
committedOct 12, 2015
Added ExitController and synchronous submission
1 parent ab59709 commit 57f6ac4

21 files changed

+421
-1879
lines changed
 

‎dist/exceptionless.d.ts

Lines changed: 0 additions & 412 deletions
Large diffs are not rendered by default.

‎dist/exceptionless.js

Lines changed: 15 additions & 1381 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎dist/exceptionless.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎dist/exceptionless.min.js

Lines changed: 1 addition & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎dist/exceptionless.min.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎dist/exceptionless.node.js

Lines changed: 112 additions & 37 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎dist/exceptionless.node.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎dist/integrations/angular.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎dist/submitSync.js

Lines changed: 66 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎dist/submitSync.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/configuration/Configuration.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ import { IEventQueue } from '../queue/IEventQueue';
1414
import { DefaultEventQueue } from '../queue/DefaultEventQueue';
1515
import { IEnvironmentInfoCollector } from '../services/IEnvironmentInfoCollector';
1616
import { IErrorParser } from '../services/IErrorParser';
17+
import { IExitController } from '../services/IExitController';
1718
import { IModuleCollector } from '../services/IModuleCollector';
1819
import { IRequestInfoCollector } from '../services/IRequestInfoCollector';
20+
import { DefaultExitController } from '../services/DefaultExitController';
1921
import { IStorage } from '../storage/IStorage';
2022
import { InMemoryStorage } from '../storage/InMemoryStorage';
2123
import { ISubmissionClient } from '../submission/ISubmissionClient';
@@ -69,6 +71,8 @@ export class Configuration implements IConfigurationSettings {
6971

7072
public queue:IEventQueue;
7173

74+
public exitController:IExitController;
75+
7276
constructor(configSettings?:IConfigurationSettings) {
7377
function inject(fn:any) {
7478
return typeof fn === 'function' ? fn(this) : fn;
@@ -89,6 +93,7 @@ export class Configuration implements IConfigurationSettings {
8993
this.submissionClient = inject(configSettings.submissionClient);
9094
this.storage = inject(configSettings.storage) || new InMemoryStorage<any>();
9195
this.queue = inject(configSettings.queue) || new DefaultEventQueue(this);
96+
this.exitController = inject(configSettings.exitController) || new DefaultExitController();
9297

9398
SettingsManager.applySavedServerSettings(this);
9499
EventPluginManager.addDefaultPlugins(this);

‎src/configuration/IConfigurationSettings.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ILog } from '../logging/ILog';
33
import { IEventQueue } from '../queue/IEventQueue';
44
import { IEnvironmentInfoCollector } from '../services/IEnvironmentInfoCollector';
55
import { IErrorParser } from '../services/IErrorParser';
6+
import { IExitController } from '../services/IExitController';
67
import { IModuleCollector } from '../services/IModuleCollector';
78
import { IRequestInfoCollector } from '../services/IRequestInfoCollector';
89
import { IStorage } from '../storage/IStorage';
@@ -21,4 +22,5 @@ export interface IConfigurationSettings {
2122
submissionClient?:ISubmissionClient;
2223
storage?:IStorage<any>;
2324
queue?:IEventQueue;
25+
exitController?: IExitController;
2426
}

‎src/exceptionless.node.ts

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { IModuleCollector } from './services/IModuleCollector';
3737
import { IRequestInfoCollector } from './services/IRequestInfoCollector';
3838
import { NodeEnvironmentInfoCollector } from './services/NodeEnvironmentInfoCollector';
3939
import { NodeErrorParser } from './services/NodeErrorParser';
40+
import { NodeExitController } from './services/NodeExitController';
4041
import { NodeRequestInfoCollector } from './services/NodeRequestInfoCollector';
4142
import { InMemoryStorage } from './storage/InMemoryStorage';
4243
import { IStorage } from './storage/IStorage';
@@ -50,22 +51,42 @@ import { EventBuilder } from 'EventBuilder';
5051
import { ExceptionlessClient } from 'ExceptionlessClient';
5152
import { Utils } from 'Utils';
5253

53-
const BEFORE_EXIT:string = 'BEFORE_EXIT';
54-
const UNCAUGHT_EXCEPTION:string = 'UNCAUGHT_EXCEPTION';
54+
const EXIT:string = 'exit';
55+
const UNCAUGHT_EXCEPTION:string = 'uncaughtException';
5556

5657
var defaults = Configuration.defaults;
5758
defaults.environmentInfoCollector = new NodeEnvironmentInfoCollector();
5859
defaults.errorParser = new NodeErrorParser();
60+
defaults.exitController = new NodeExitController();
5961
defaults.requestInfoCollector = new NodeRequestInfoCollector();
6062
defaults.submissionClient = new NodeSubmissionClient();
6163

64+
function getListenerCount(emitter, event):number {
65+
if (emitter.listenerCount) {
66+
return emitter.listenerCount(event);
67+
}
68+
69+
return require("events").listenerCount(emitter, event);
70+
}
71+
6272
process.on(UNCAUGHT_EXCEPTION, function (error:Error) {
6373
ExceptionlessClient.default.submitUnhandledException(error, UNCAUGHT_EXCEPTION);
74+
75+
/*
76+
* Default Node behavior: If this is the only uncaught-listener, we still exit.
77+
* Discussion: https://nodejs.org/api/process.html#process_event_uncaughtexception
78+
*/
79+
var uncaughtListenerCount = getListenerCount(process, UNCAUGHT_EXCEPTION);
80+
if (uncaughtListenerCount <= 1) {
81+
process.exit(1);
82+
}
6483
});
6584

66-
process.on(BEFORE_EXIT, function (code:number) {
85+
process.on(EXIT, function (code:number) {
6786
/**
6887
* exit codes: https://nodejs.org/api/process.html#process_event_exit
88+
* From now on, only synchronous code may run. As soon as this method
89+
* ends, the application inevitably will exit.
6990
*/
7091
function getExitCodeReason(code:number): string {
7192
if (code === 1) {
@@ -116,12 +137,16 @@ process.on(BEFORE_EXIT, function (code:number) {
116137
}
117138

118139
var client = ExceptionlessClient.default;
140+
var config = client.config;
119141
var message = getExitCodeReason(code);
142+
120143
if (message !== null) {
121-
client.submitLog(BEFORE_EXIT, message, 'Error')
144+
client.submitLog(EXIT, message, 'Error')
122145
}
123146

124-
client.config.queue.process()
147+
config.exitController.processExit(config);
148+
149+
// Application will now exit.
125150
});
126151

127152
(<any>Error).stackTraceLimit = Infinity;

‎src/node.fix.d.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
TODO: Fix in DefinitelyTyped, remove this file, remove reference from tsconfig.node.json
3+
4+
Pull requests:
5+
https://github.com/borisyankov/DefinitelyTyped/pull/6185
6+
*/
7+
8+
declare module "child_process" {
9+
export function spawnSync(command: string, args?: string[], options?: {
10+
cwd?: string;
11+
input?: string | Buffer;
12+
stdio?: any;
13+
env?: any;
14+
uid?: number;
15+
gid?: number;
16+
timeout?: number;
17+
maxBuffer?: number;
18+
killSignal?: string;
19+
encoding?: string;
20+
}): {
21+
pid: number;
22+
output: string[];
23+
stdout: string | Buffer;
24+
stderr: string | Buffer;
25+
status: number;
26+
signal: string;
27+
error: Error;
28+
};
29+
}

‎src/services/DefaultExitController.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Configuration } from '../configuration/Configuration';
2+
import { IExitController } from './IExitController';
3+
4+
export class DefaultExitController implements IExitController {
5+
6+
public get isApplicationExiting() {
7+
return false;
8+
}
9+
10+
public processExit(config:Configuration): void {
11+
}
12+
}

‎src/services/IExitController.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { Configuration } from '../configuration/Configuration';
2+
3+
export interface IExitController {
4+
isApplicationExiting:boolean;
5+
processExit(config:Configuration):void;
6+
}

‎src/services/NodeExitController.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Configuration } from '../configuration/Configuration';
2+
import { IExitController } from './IExitController';
3+
4+
export class NodeExitController implements IExitController {
5+
6+
private _isApplicationExiting: boolean;
7+
8+
public get isApplicationExiting() {
9+
return this._isApplicationExiting;
10+
}
11+
12+
public processExit(config:Configuration):void {
13+
this._isApplicationExiting = true;
14+
config.queue.process();
15+
}
16+
}

‎src/submission/NodeSubmissionClient.ts

Lines changed: 27 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,57 +3,48 @@ import { IEvent } from '../models/IEvent';
33
import { IUserDescription } from '../models/IUserDescription';
44
import { DefaultSubmissionClient } from './DefaultSubmissionClient';
55
import { SettingsResponse } from './SettingsResponse';
6+
import { NodeSubmissionRequest, NodeSubmissionCallback, submitRequest } from './NodeSubmissionRequest';
67
import { SubmissionResponse } from './SubmissionResponse';
78
import { Utils } from '../Utils';
89

910
import http = require('http');
10-
import https = require('https');
11-
import url = require('url');
11+
import child = require('child_process');
1212

1313
export class NodeSubmissionClient extends DefaultSubmissionClient {
1414
constructor() {
1515
super();
1616
this.configurationVersionHeader = this.configurationVersionHeader.toLowerCase();
1717
}
1818

19-
public sendRequest(config:Configuration, method:string, path:string, data:string, callback: (status:number, message:string, data?:string, headers?:Object) => void): void {
20-
function complete(response:http.IncomingMessage, responseBody:string, responseHeaders:Object) {
21-
var message:string;
22-
if (response.statusCode === 0) {
23-
message = 'Unable to connect to server.';
24-
} else if (response.statusCode < 200 || response.statusCode > 299) {
25-
message = response.statusMessage || (<any>response).message;
26-
}
19+
public sendRequest(config: Configuration, method: string, path: string, data: string, callback: NodeSubmissionCallback): void {
20+
var request: NodeSubmissionRequest = {
21+
method,
22+
path,
23+
data,
24+
serverUrl: config.serverUrl,
25+
apiKey: config.apiKey,
26+
userAgent: config.userAgent
27+
};
2728

28-
callback(response.statusCode || 500, message, responseBody, responseHeaders);
29+
var exitController = config.exitController;
30+
if (exitController.isApplicationExiting) {
31+
this.sendRequestSync(request, callback);
32+
} else {
33+
submitRequest(request, callback);
2934
}
35+
}
3036

31-
var parsedHost = url.parse(config.serverUrl);
32-
var options:https.RequestOptions = {
33-
auth: `client:${config.apiKey}`,
34-
headers: {},
35-
hostname: parsedHost.hostname,
36-
method: method,
37-
port: parsedHost.port && parseInt(parsedHost.port),
38-
path: path
39-
};
37+
private sendRequestSync(request: NodeSubmissionRequest, callback: NodeSubmissionCallback): void {
38+
var requestJson = JSON.stringify(request);
39+
var res = child.spawnSync(process.execPath, [require.resolve('./submitSync.js')],
40+
{
41+
input: requestJson,
42+
stdio: ['pipe', 'pipe', process.stderr]
43+
});
4044

41-
if (method === 'POST') {
42-
options.headers = {
43-
'Content-Type': 'application/json',
44-
'Content-Length': data.length
45-
}
46-
}
47-
48-
options.headers['User-Agent'] = config.userAgent;
49-
var request:http.ClientRequest = (parsedHost.protocol === 'https' ? https : http).request(options, (response:http.IncomingMessage) => {
50-
var body = '';
51-
response.setEncoding('utf8');
52-
response.on('data', (chunk) => body += chunk);
53-
response.on('end', () => complete(response, body, response.headers));
54-
});
45+
var out = res.stdout.toString();
46+
var result = JSON.parse(out);
5547

56-
request.on('error', (error:Error) => callback(500, error.message));
57-
request.end(data);
48+
callback(result.status, result.message, result.data, result.headers);
5849
}
5950
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import http = require('http');
2+
import https = require('https');
3+
import url = require('url');
4+
5+
export interface NodeSubmissionCallback {
6+
(status: number, message: string, data?: string, headers?: Object): void
7+
}
8+
9+
export interface NodeSubmissionRequest {
10+
serverUrl: string;
11+
apiKey: string;
12+
userAgent: string;
13+
method: string;
14+
path: string;
15+
data: string;
16+
}
17+
18+
function complete(response: http.IncomingMessage, responseBody: string, responseHeaders: Object, callback:NodeSubmissionCallback): void {
19+
var message: string;
20+
if(response.statusCode === 0) {
21+
message = 'Unable to connect to server.';
22+
} else if (response.statusCode < 200 || response.statusCode > 299) {
23+
message = response.statusMessage || (<any>response).message;
24+
}
25+
26+
callback(response.statusCode || 500, message, responseBody, responseHeaders);
27+
}
28+
29+
export function submitRequest(request: NodeSubmissionRequest, callback: NodeSubmissionCallback): void {
30+
var parsedHost = url.parse(request.serverUrl);
31+
32+
var options: https.RequestOptions = {
33+
auth: `client:${request.apiKey}`,
34+
headers: {},
35+
hostname: parsedHost.hostname,
36+
method: request.method,
37+
port: parsedHost.port && parseInt(parsedHost.port),
38+
path: request.path
39+
};
40+
41+
options.headers['User-Agent'] = request.userAgent;
42+
43+
if (request.method === 'POST') {
44+
options.headers = {
45+
'Content-Type': 'application/json',
46+
'Content-Length': request.data.length
47+
}
48+
}
49+
50+
var protocol = (parsedHost.protocol === 'https' ? https : http);
51+
var clientRequest: http.ClientRequest = protocol.request(options, (response: http.IncomingMessage) => {
52+
var body = '';
53+
response.setEncoding('utf8');
54+
response.on('data', (chunk) => body += chunk);
55+
response.on('end', () => complete(response, body, response.headers, callback));
56+
});
57+
58+
clientRequest.on('error', (error: Error) => callback(500, error.message));
59+
clientRequest.end(request.data);
60+
}

‎src/submitSync.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { NodeSubmissionRequest, submitRequest } from './submission/NodeSubmissionRequest';
2+
3+
import * as stream from 'stream';
4+
import { StringDecoder } from 'string_decoder';
5+
6+
var decoder = new StringDecoder('utf8');
7+
var strings: string[] = [];
8+
9+
var jsonStream = new stream.Writable();
10+
jsonStream._write = (chunk: Buffer|string, encoding: string, next: Function) => {
11+
strings.push(decoder.write(<Buffer>chunk));
12+
next();
13+
};
14+
15+
jsonStream.on("finish", () => {
16+
var json = strings.join("");
17+
var request:NodeSubmissionRequest = JSON.parse(json);
18+
19+
submitRequest(request, (status, message, data, headers) => {
20+
var result = {
21+
status,
22+
message,
23+
data,
24+
headers
25+
};
26+
process.stdout.write(JSON.stringify(result));
27+
process.exit(0);
28+
});
29+
});
30+
31+
process.stdin.pipe(jsonStream);

‎src/tsconfig.node.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,17 @@
1010
"exceptionless.node": {
1111
"files": [
1212
"typings/node/node.d.ts",
13+
"node.fix.d.ts",
1314
"typings/stack-trace/stack-trace.d.ts",
1415

1516
"exceptionless.node.ts"
1617
]
1718
},
1819
"submitSync": {
1920
"files": [
21+
"typings/node/node.d.ts",
22+
"node.fix.d.ts",
23+
2024
"submitSync.ts"
2125
]
2226
}

0 commit comments

Comments
 (0)
Please sign in to comment.