Ohif 168 proper error handling (#111)

* Adding a better error handling for DICOMWeb getJson method

* Adding a better error handling for DICOMWeb getBulkData method

* Fixinf some typos. Using console.error to log errors

* Better error handling for DIMSE connections

* Using meteor error in StudyListSearch to better display same error in the client

* Using Meteor.Error for a better pattern. Added an error message in the study list. created connection error types to DICOMWeb connection errors

* Changing error types for server connection. Added better errors for DIMSE server on sockets

* Improving viewer response to error being throw when not finding server to display studies

* OHIF-168: adding catches clauses for retrieveStudyMetada method

* OHIF-168: fixing typo in DIMSE.js

* OHIF-168: setting the session variable  to false when starting a new studylist search

* OHIF-168: Using OHIF.log instead of console

* OHIF-168: removing parsing into string for OHIF.log in Connection.js

* OHIF-168: improving error message visibility in study viewer
This commit is contained in:
André Botelho Almeida 2017-09-19 12:44:39 -03:00 committed by Erik Ziegler
parent 4c5dcfeab0
commit af4b8b45f5
18 changed files with 285 additions and 88 deletions

View File

@ -1,3 +1,5 @@
import { OHIF } from 'meteor/ohif:core';
const ASCII = 'ascii';
const http = Npm.require('http')
const url = Npm.require('url');
@ -165,6 +167,14 @@ function makeRequest(geturl, options, callback) {
data.push(chunk);
});
resp.on('error', function (responseError) {
OHIF.log.error('There was an error in the DICOMWeb Server');
OHIF.log.error(responseError.stack);
OHIF.log.trace();
callback(responseError, null);
});
resp.on('end', function() {
try {
callback(null, parseResponse(resp.headers, Buffer.concat(data)));
@ -175,6 +185,15 @@ function makeRequest(geturl, options, callback) {
});
req.on('error', function (requestError) {
OHIF.log.error('Couldn\'t connect to DICOMWeb server.');
OHIF.log.error('Make sure you are trying to connect to the right server and that it is up and running.');
OHIF.log.error(requestError.stack);
OHIF.log.trace();
callback(requestError, null);
});
req.end();
}
@ -185,7 +204,7 @@ const makeRequestSync = Meteor.wrapAsync(makeRequest);
DICOMWeb.getBulkData = function(geturl, options) {
if (options.logRequests) {
console.log(geturl);
OHIF.log.info(geturl);
}
if (options.logTiming) {
@ -199,7 +218,7 @@ DICOMWeb.getBulkData = function(geturl, options) {
}
if (options.logResponses) {
console.log(result);
OHIF.log.info(result);
}
if (!Buffer.isBuffer(result)) {

View File

@ -1,4 +1,5 @@
import { Meteor } from 'meteor/meteor';
import { OHIF } from 'meteor/ohif:core';
const http = Npm.require('http');
const https = Npm.require('https');
@ -52,14 +53,35 @@ function makeRequest(geturl, options, callback) {
}
let output = '';
resp.setEncoding('utf8');
resp.on('data', function(chunk) {
output += chunk;
resp.on('data', function(chunk){
output += chunk;
});
resp.on('end', function() {
callback(null, { data: JSON.parse(output) });
resp.on('error', function (responseError) {
OHIF.log.error('There was an error in the DICOMWeb Server')
OHIF.log.error(error.stack);
OHIF.log.trace();
callback(new Meteor.Error('server-internal-error', responseError.message), null);
});
resp.on('end', function(){
callback(null, { data: JSON.parse(output) });
});
});
req.on('error', function (requestError) {
OHIF.log.error('Couldn\'t connect to DICOMWeb server.');
OHIF.log.error('Make sure you are trying to connect to the right server and that it is up and running.');
OHIF.log.error(requestError.stack);
OHIF.log.trace();
callback(new Meteor.Error('server-connection-error', requestError.message), null);
});
req.end();
}
@ -67,7 +89,7 @@ const makeRequestSync = Meteor.wrapAsync(makeRequest);
DICOMWeb.getJSON = function(geturl, options) {
if (options.logRequests) {
console.log(geturl);
OHIF.log.info(geturl);
}
if (options.logTiming) {
@ -81,7 +103,7 @@ DICOMWeb.getJSON = function(geturl, options) {
}
if (options.logResponses) {
console.log(result);
OHIF.log.info(result);
}
return result;

View File

@ -1,3 +1,5 @@
import { OHIF } from 'meteor/ohif:core';
var EventEmitter = Npm.require('events').EventEmitter;
function time() {
@ -41,40 +43,52 @@ CSocket = function(socket, options) {
var o = this;
this.socket.on('connect', function() {
console.log('Connect');
OHIF.log.info('Connect');
o.ready();
});
this.socket.on('data', function(data) {
o.received(data);
});
this.socket.on('error', function(he) {
console.log('Error: ', he);
throw 'Error: ' + he;
this.socket.on('error', function(socketError) {
OHIF.log.error('There was an error with DIMSE connection socket.');
OHIF.log.error(socketError.stack);
OHIF.log.trace();
o.emit('error', new Meteor.Error('server-internal-error', socketError.message));
});
this.socket.on('timeout', function(he) {
console.log('Timeout: ', he);
throw 'Timeout: ' + he;
this.socket.on('timeout', function(socketError) {
OHIF.log.error('The connection timed out. The server is not responding.');
OHIF.log.error(socketError.stack);
OHIF.log.trace();
o.emit('error', new Meteor.Error('server-connection-error', socketError.message));
});
this.socket.on('close', function() {
if (o.intervalId) {
clearInterval(o.intervalId);
}
o.connected = false;
console.log('Connection closed');
o.emit('close');
OHIF.log.info('Connection closed');
o.emit('close');
});
this.on('released', function() {
this.released();
});
this.on('aborted', function(pdu) {
console.warn('Association aborted with reason ' + pdu.reason);
OHIF.log.warn('Association aborted with reason ' + pdu.reason);
this.released();
});
this.on('message', function(pdvs) {
this.receivedMessage(pdvs);
});
});
};
util.inherits(CSocket, EventEmitter);
@ -213,7 +227,7 @@ CSocket.prototype.released = function() {
};
CSocket.prototype.ready = function() {
console.log('Connection established');
OHIF.log.info('Connection established');
this.connected = true;
this.started = time();
@ -237,7 +251,7 @@ CSocket.prototype.checkIdle = function() {
};
CSocket.prototype.idleClose = function() {
console.log('Exceed idle time, closing connection');
OHIF.log.info('Exceed idle time, closing connection');
this.release();
};
@ -283,7 +297,7 @@ CSocket.prototype.process = function(data) {
}
}
} else {
console.log('Data received');
OHIF.log.info('Data received');
var newData = Buffer.concat([this.receiving, data], this.receiving.length + data.length),
pduLength = newData.length - 6;
@ -398,7 +412,7 @@ CSocket.prototype.receivedMessage = function(pdv) {
}
if (msg.failure()) {
console.log('message failed with status ', msg.getStatus().toString(16));
OHIF.log.info('message failed with status ', msg.getStatus().toString(16));
}
listener.emit('response', msg);
@ -452,7 +466,10 @@ CSocket.prototype.receivedMessage = function(pdv) {
} else {
throw 'Where does this c-store came from?';
}
} else console.log('move ', moveMessageId);
} else{
OHIF.log.info('move ', moveMessageId);
}
//this.storeResponse(useId, msg);
}
}
@ -508,7 +525,7 @@ CSocket.prototype.sendMessage = function(context, command, dataset) {
msgData.command = command;
this.messages[messageId] = msgData;
console.log('Sending command ' + command.typeString());
OHIF.log.info('Sending command ' + command.typeString());
this.send(pdata);
if (dataset && typeof dataset === 'object') {
dataset.setSyntax(syntax);

View File

@ -1,4 +1,5 @@
import { _ } from 'meteor/underscore';
import { OHIF } from 'meteor/ohif:core';
// Uses NodeJS 'net'
// https://nodejs.org/api/net.html
@ -62,10 +63,10 @@ Connection.prototype.addPeer = function(options) {
//start listening
peer.server = net.createServer();
peer.server.listen(options.port, options.host, function() {
console.log('listening on %j', this.address());
OHIF.log.info('listening on', this.address());
});
peer.server.on('error', function(err) {
console.log('server error %j', err);
OHIF.log.info('server error', err);
});
peer.server.on('connection', nativeSocket => {
//incoming connections
@ -90,13 +91,13 @@ Connection.prototype.selectPeer = function(aeTitle) {
Connection.prototype._sendFile = function(socket, sHandle, file, maxSend, metaLength, list) {
var fileNameText = typeof file.file === 'string' ? file.file : 'buffer';
console.log('Sending file ' + fileNameText);
OHIF.log.info(`Sending file ${fileNameText}`);
var useContext = socket.getContextByUID(file.context);
var self = this;
PDU.generatePDatas(useContext.id, file.file, maxSend, null, metaLength, function(err, handle) {
if (err) {
console.log('Error while sending file');
OHIF.log.info('Error while sending file');
return;
}
@ -119,7 +120,7 @@ Connection.prototype._sendFile = function(socket, sHandle, file, maxSend, metaLe
});
store.on('response', function(msg) {
var statusText = msg.getStatus().toString(16);
console.log('STORE reponse with status', statusText);
OHIF.log.info('STORE reponse with status', statusText);
var error = null;
if (msg.failure()) {
error = new Error(statusText);
@ -153,7 +154,7 @@ Connection.prototype.storeInstances = function(fileList) {
return;
}
console.log('Dicom file ' + (typeof bufferOrFile === 'string' ? bufferOrFile : 'buffer') + ' found');
OHIF.log.info(`Dicom file ${(typeof bufferOrFile === 'string' ? bufferOrFile : 'buffer')} found`);
lastProcessedMetaLength = metaLength;
var syntax = metaMessage.getValue(0x00020010);
var sopClassUID = metaMessage.getValue(0x00020002);
@ -248,6 +249,7 @@ Connection.prototype.addSocket = function(hostAE, socket) {
};
Connection.prototype.associate = function(options, callback) {
const self = this;
var hostAE = options.hostAE ? options.hostAE : this.defaultPeer;
var sourceAE = options.sourceAE ? options.sourceAE : this.defaultServer;
@ -264,7 +266,7 @@ Connection.prototype.associate = function(options, callback) {
socket.once('associated', callback);
}
console.log('Starting Connection...');
OHIF.log.info('Starting Connection...');
socket.setCalledAe(hostAE);
socket.setCallingAE(sourceAE);
@ -279,7 +281,7 @@ Connection.prototype.associate = function(options, callback) {
if (options.contexts) {
socket.setPresentationContexts(options.contexts);
} else {
throw 'Contexts must be specified';
throw new Meteor.Error('Contexts must be specified');
}
socket.associate();

View File

@ -1,3 +1,5 @@
import { OHIF } from 'meteor/ohif:core';
var Future = Npm.require('fibers/future');
DIMSE = {
@ -75,23 +77,34 @@ DIMSE.associate = function(contexts, callback, options) {
};
options = Object.assign(defaults, options);
console.log('Associating...');
try {
conn.associate(options, function(pdu) {
// associated
console.log('==Associated');
callback.call(this, pdu);
});
} catch(error) {
console.error('dimse-associate: ' + error);
throw new Meteor.Error('dimse-associate', error);
}
OHIF.log.info('Associating...');
const socket = conn.associate(options, function(pdu) {
// associated
OHIF.log.info('==Associated');
callback(null, pdu);
});
socket.on('error', function (error) {
callback(error, null);
});
socket.on('timeout', function (error) {
callback(error, null);
});
};
DIMSE.retrievePatients = function(params, options) {
//var start = new Date();
var future = new Future;
DIMSE.associate([C.SOP_PATIENT_ROOT_FIND], function(pdu) {
DIMSE.associate([C.SOP_PATIENT_ROOT_FIND], function(error, pdu) {
if (error) {
OHIF.log.error('Could not retrieve patients');
OHIF.log.trace();
return future.return([]);
}
var defaultParams = {
0x00100010: '',
0x00100020: '',
@ -124,7 +137,14 @@ DIMSE.retrievePatients = function(params, options) {
DIMSE.retrieveStudies = function(params, options) {
//var start = new Date();
var future = new Future;
DIMSE.associate([C.SOP_STUDY_ROOT_FIND], function(pdu) {
DIMSE.associate([C.SOP_STUDY_ROOT_FIND], function(error, pdu) {
if (error) {
OHIF.log.error('Could not retrieve studies');
OHIF.log.trace();
return future.throw(error);
}
var defaultParams = {
0x0020000D: '',
0x00080060: '',
@ -201,7 +221,14 @@ DIMSE.retrieveInstancesByStudyOnly = function(studyInstanceUID, params, options)
}
var future = new Future;
DIMSE.associate([C.SOP_STUDY_ROOT_FIND], function(pdu) {
DIMSE.associate([C.SOP_STUDY_ROOT_FIND], function(error, pdu) {
if (error) {
OHIF.log.error('Could not retrieve Instances By Study');
OHIF.log.trace();
return future.throw(error);
}
var defaultParams = {
0x0020000D: studyInstanceUID,
0x00080005: '',
@ -244,7 +271,14 @@ DIMSE.retrieveInstancesByStudyOnly = function(studyInstanceUID, params, options)
DIMSE.retrieveSeries = function(studyInstanceUID, params, options) {
var future = new Future;
DIMSE.associate([C.SOP_STUDY_ROOT_FIND], function(pdu) {
DIMSE.associate([C.SOP_STUDY_ROOT_FIND], function(error, pdu) {
if (error) {
OHIF.log.error('Could not retrieve series');
OHIF.log.trace();
return future.return([]);
}
var defaultParams = {
0x0020000D: studyInstanceUID ? studyInstanceUID : '',
0x00080005: '',
@ -280,7 +314,14 @@ DIMSE.retrieveSeries = function(studyInstanceUID, params, options) {
DIMSE.retrieveInstances = function(studyInstanceUID, seriesInstanceUID, params, options) {
var future = new Future;
DIMSE.associate([C.SOP_STUDY_ROOT_FIND], function(pdu) {
DIMSE.associate([C.SOP_STUDY_ROOT_FIND], function(error, pdu) {
if (error) {
OHIF.log.error('Could not retrieve instances');
OHIF.log.trace();
return future.throw(error);
}
var defaultParams = getInstanceRetrievalParams(studyInstanceUID, seriesInstanceUID);
var result = this.findInstances(Object.assign(defaultParams, params)),
o = this;
@ -309,7 +350,14 @@ DIMSE.storeInstances = function(fileList, callback) {
};
DIMSE.moveInstances = function(studyInstanceUID, seriesInstanceUID, sopInstanceUID, sopClassUID, params) {
DIMSE.associate([C.SOP_STUDY_ROOT_MOVE, sopClassUID], function() {
DIMSE.associate([C.SOP_STUDY_ROOT_MOVE, sopClassUID], function(error) {
if (error) {
OHIF.log.error('Could not move instances');
OHIF.log.trace();
return;
}
var defaultParams = {
0x0020000D: studyInstanceUID ? studyInstanceUID : '',
0x0020000E: seriesInstanceUID ? seriesInstanceUID : '',

View File

@ -59,6 +59,11 @@ function renderIntoViewport(viewportIndex, studyInstanceUid, seriesInstanceUid,
}
findAndRenderDisplaySet(study.displaySets, viewportIndex, studyInstanceUid, seriesInstanceUid, sopInstanceUid, renderedCallback);
}).catch(error => {
OHIF.log.error(`There was an error trying to retrieve the study\'s metadata for studyInstanceUid: ${studyInstanceUid}`);
OHIF.log.error(error.stack);
OHIF.log.trace();
});
}
}

View File

@ -80,9 +80,13 @@
{{#if session "showLoadingText"}}
{{>loadingText}}
{{else}}
{{#unless numberOfStudies}}
<div class="notFound">No matching results</div>
{{/unless}}
{{#if session "serverError"}}
<div class="notFound">There was an error fetching studies</div>
{{else}}
{{#unless numberOfStudies}}
<div class="notFound">No matching results</div>
{{/unless}}
{{/if}}
{{/if}}
</div>
</template>

View File

@ -7,6 +7,7 @@ import { moment } from 'meteor/momentjs:moment';
import { OHIF } from 'meteor/ohif:core';
Session.setDefault('showLoadingText', true);
Session.setDefault('serverError', false);
Template.studylistResult.helpers({
/**
@ -132,6 +133,9 @@ function search() {
// Show loading message
Session.set('showLoadingText', true);
// Hiding error message
Session.set('serverError', false);
// Create the filters to be used for the StudyList Search
filter = {
patientName: getFilter($('input#patientName').val()),
@ -152,14 +156,27 @@ function search() {
Meteor.call('StudyListSearch', filter, (error, studies) => {
OHIF.log.info('StudyListSearch');
// Hide loading text
Session.set('showLoadingText', false);
if (error) {
OHIF.log.warn(error);
Session.set('serverError', true);
const errorType = error.error;
if (errorType === 'server-connection-error') {
OHIF.log.error('There was an error connecting to the DICOM server, please verify if it is up and running.');
} else if (errorType === 'server-internal-error') {
OHIF.log.error('There was an internal error with the DICOM server');
} else {
OHIF.log.error('For some reason we could not list the studies.')
}
OHIF.log.error(error.stack);
return;
}
// Hide loading text
Session.set('showLoadingText', false);
if (!studies) {
OHIF.log.warn('No studies found');
return;

View File

@ -53,7 +53,7 @@ export class OHIFStudyMetadataSource extends OHIF.viewerbase.StudyMetadataSource
OHIFStudyMetadataSource._updateStudyCollections(studyMetadata);
resolve(studyMetadata);
}, reject);
}).catch(reject);
});
}

View File

@ -43,6 +43,11 @@ queryStudiesWithProgress = function(studiesToQuery) {
dialog.done(studiesQueried);
}, () => {
dialog.cancel();
}).catch(error => {
OHIF.log.error('There was an error retrieving all studies metadeta.');
OHIF.log.error(error.stack);
OHIF.log.trace();
});
}
}

View File

@ -34,8 +34,20 @@ OHIF.studylist.retrieveStudyMetadata = (studyInstanceUid, seriesInstanceUids) =>
console.timeEnd('retrieveStudyMetadata');
if (error) {
OHIF.log.error(error);
reject(error);
const errorType = error.error;
let errorMessage = '';
if (errorType === 'server-connection-error') {
errorMessage = 'There was an error connecting to the DICOM server, please verify if it is up and running.'
} else if (errorType === 'server-internal-error') {
errorMessage = `There was an internal error with the DICOM server getting metadeta for ${studyInstanceUid}`;
} else {
errorMessage = `For some reason we could not retrieve the study\'s metadata for ${studyInstanceUid}.`;
}
OHIF.log.error(errorMessage);
OHIF.log.error(error.stack);
reject(`GetStudyMetadata: ${errorMessage}`);
return;
}

View File

@ -14,13 +14,19 @@ Meteor.methods({
const server = OHIF.servers.getCurrentServer();
if (!server) {
throw 'No properly configured server was available over DICOMWeb or DIMSE.';
throw new Meteor.Error('improper-server-config', 'No properly configured server was available over DICOMWeb or DIMSE.');
}
if (server.type === 'dicomWeb') {
return Services.WADO.RetrieveMetadata(server, studyInstanceUid);
} else if (server.type === 'dimse') {
return Services.DIMSE.RetrieveMetadata(studyInstanceUid);
try {
if (server.type === 'dicomWeb') {
return Services.WADO.RetrieveMetadata(server, studyInstanceUid);
} else if (server.type === 'dimse') {
return Services.DIMSE.RetrieveMetadata(studyInstanceUid);
}
} catch (error) {
OHIF.log.trace();
throw error;
}
}
});

View File

@ -13,13 +13,19 @@ Meteor.methods({
const server = OHIF.servers.getCurrentServer();
if (!server) {
throw 'No properly configured server was available over DICOMWeb or DIMSE.';
throw new Meteor.Error('improper-server-config', 'No properly configured server was available over DICOMWeb or DIMSE.');
}
if (server.type === 'dicomWeb') {
return Services.QIDO.Studies(server, filter);
} else if (server.type === 'dimse') {
return Services.DIMSE.Studies(filter);
try {
if (server.type === 'dicomWeb') {
return Services.QIDO.Studies(server, filter);
} else if (server.type === 'dimse') {
return Services.DIMSE.Studies(filter);
}
} catch (error) {
OHIF.log.trace();
throw error;
}
}
});

View File

@ -1,3 +1,5 @@
import { OHIF } from 'meteor/ohif:core';
/**
* Creates a QIDO URL given the server settings and a study instance UID
* @param server
@ -69,15 +71,24 @@ function resultDataToStudyMetadata(server, studyInstanceUid, resultData) {
* Retrieve a set of instances using a QIDO call
* @param server
* @param studyInstanceUid
* @throws ECONNREFUSED
* @returns {{wadoUriRoot: String, studyInstanceUid: String, seriesList: Array}}
*/
Services.QIDO.Instances = function(server, studyInstanceUid) {
var url = buildUrl(server, studyInstanceUid);
var result = DICOMWeb.getJSON(url, server.requestOptions);
return {
wadoUriRoot: server.wadoUriRoot,
studyInstanceUid: studyInstanceUid,
seriesList: resultDataToStudyMetadata(server, studyInstanceUid, result.data)
};
try {
var result = DICOMWeb.getJSON(url, server.requestOptions);
return {
wadoUriRoot: server.wadoUriRoot,
studyInstanceUid: studyInstanceUid,
seriesList: resultDataToStudyMetadata(server, studyInstanceUid, result.data)
};
} catch (error) {
OHIF.log.trace();
throw error;
}
};

View File

@ -1,3 +1,5 @@
import { OHIF } from 'meteor/ohif:core';
/**
* Creates a QIDO date string for a date range query
* Assumes the year is positive, at most 4 digits long.
@ -91,6 +93,14 @@ function resultDataToStudies(resultData) {
Services.QIDO.Studies = function(server, filter) {
var url = filterToQIDOURL(server, filter);
var result = DICOMWeb.getJSON(url, server.requestOptions);
return resultDataToStudies(result.data);
try {
var result = DICOMWeb.getJSON(url, server.requestOptions);
return resultDataToStudies(result.data);
} catch (error) {
OHIF.log.trace();
throw error;
}
};

View File

@ -1,3 +1,4 @@
import { OHIF } from 'meteor/ohif:core';
import { parseFloatArray } from '../../lib/parseFloatArray';
/**
@ -148,7 +149,7 @@ function getPaletteColors(server, instance, lutDescriptor) {
paletteColorCache.add(entry);
}
} catch (error) {
console.log(`(${error.name}) ${error.message}`);
OHIF.log.error(`(${error.name}) ${error.message}`);
}
}
@ -339,15 +340,21 @@ function resultDataToStudyMetadata(server, studyInstanceUid, resultData) {
Services.WADO.RetrieveMetadata = function(server, studyInstanceUid) {
var url = buildUrl(server, studyInstanceUid);
var result = DICOMWeb.getJSON(url, server.requestOptions);
try {
var result = DICOMWeb.getJSON(url, server.requestOptions);
var study = resultDataToStudyMetadata(server, studyInstanceUid, result.data);
if (!study) {
study = {};
}
study.wadoUriRoot = server.wadoUriRoot;
study.studyInstanceUid = studyInstanceUid;
return study;
} catch (error) {
OHIF.log.trace();
var study = resultDataToStudyMetadata(server, studyInstanceUid, result.data);
if (!study) {
study = {};
throw error;
}
study.wadoUriRoot = server.wadoUriRoot;
study.studyInstanceUid = studyInstanceUid;
return study;
};

View File

@ -2,3 +2,4 @@
font-weight: 300
padding: 30px
text-align: center
color: #ffffff

View File

@ -117,6 +117,11 @@ Template.studyTimepointStudy.events({
$studies.trigger('loadEnded');
instance.select(isQuickSwitch);
}, 1);
}).catch(error => {
OHIF.log.error(`There was an error trying to retrieve the study\'s metadata for studyInstanceUid: ${studyInstanceUid}`);
OHIF.log.error(error.stack);
OHIF.log.trace();
});
} else {
studyData.seriesList = alreadyLoaded.seriesList;