commit 89cb135329caec5666c02d0266dd657c4cc48c42
parent c32e0526cb1684cd76dcaaf79201bfee09a8d1d4
Author: Sergej Orlov <wladimirych@gmail.com>
Date: Sun, 5 Mar 2017 18:37:02 +0300
refactored error handling, added logging to sentry
Diffstat:
15 files changed, 108 insertions(+), 59 deletions(-)
diff --git a/src/lib/clipboardCopy/index.js b/src/lib/clipboardCopy/index.js
@@ -1,4 +1,5 @@
import './style.css';
+import logging from 'lib/logging';
function showNotification(message, mouseEvent) {
var el = document.createElement('div');
@@ -32,6 +33,7 @@ function copyToClipboard(s, mouseEvent) {
document.execCommand('copy');
showNotification('Copied', mouseEvent);
} catch (e) {
+ logging.captureException(e, {extra: {description: 'clipborad to copy failed'}});
prompt("Copy to clipboard: Ctrl+C, Enter", s);
} finally {
document.body.removeChild(ta);
diff --git a/src/lib/leaflet.control.elevation-profile/index.js b/src/lib/leaflet.control.elevation-profile/index.js
@@ -3,7 +3,8 @@ import './elevation-profile.css';
import {fetch} from 'lib/xhr-promise';
import config from 'config';
import 'lib/leaflet.control.commons';
-import {formatXhrError, notify} from 'lib/notifications';
+import {notify} from 'lib/notifications';
+import logging from 'lib/logging';
function createSvg(tagName, attributes, parent) {
var element = document.createElementNS('http://www.w3.org/2000/svg', tagName);
@@ -221,7 +222,8 @@ L.Control.ElevationProfile = L.Class.extend({
}
)
.catch((e) => {
- notify(e);
+ logging.captureException(e, {extra: {description: 'while getting elevation'}});
+ notify(`Failed to get elevation data: ${e.message}`);
});
this.values = null;
@@ -789,9 +791,7 @@ L.Control.ElevationProfile = L.Class.extend({
function(xhr) {
return parseResponse(xhr.responseText);
}
- ).catch((xhr) => {
- throw new Error(formatXhrError(xhr, 'elevation data'))
- });
+ );
},
onCloseButtonClick: function() {
this.removeFrom(this._map);
diff --git a/src/lib/leaflet.control.jnx/index.js b/src/lib/leaflet.control.jnx/index.js
@@ -7,6 +7,7 @@ import Contextmenu from 'lib/contextmenu';
import {makeJnxFromLayer, minZoom} from './jnx-maker';
import {saveAs} from 'browser-filesaver';
import {notify} from 'lib/notifications';
+import logging from 'lib/logging';
L.Control.JNX = L.Control.extend({
includes: L.Mixin.Events,
@@ -89,6 +90,7 @@ L.Control.JNX = L.Control.extend({
},
makeJnx: function(layer, layerName, zoom) {
+ logging.captureBreadcrumbWithUrl({message: 'start making jnx'});
this.makingJnx(true);
this.downloadProgressDone(0);
@@ -97,7 +99,11 @@ L.Control.JNX = L.Control.extend({
const fileName = `nakarte.tk_${sanitizedLayerName}_z${zoom}.jnx`;
makeJnxFromLayer(layer, layerName, zoom, bounds, this.notifyProgress.bind(this))
.then((fileData) => saveAs(fileData, fileName, true))
- .catch((e) => notify(e.message))
+ .catch((e) => {
+ logging.captureException(e);
+ notify(`Failed to create JNX: ${e.message}`);
+ }
+ )
.then(() => this.makingJnx(false));
},
diff --git a/src/lib/leaflet.control.jnx/jnx-maker.js b/src/lib/leaflet.control.jnx/jnx-maker.js
@@ -3,7 +3,6 @@ import {JnxWriter} from './jnx-encoder';
import {getTempMap, disposeMap} from 'lib/leaflet.layer.rasterize';
import {XHRQueue} from 'lib/xhr-promise';
import {arrayBufferToString, stringToArrayBuffer} from 'lib/binary-strings';
-import {formatXhrError} from 'lib/notifications';
const defaultXHROptions = {
responseType: 'arraybuffer',
@@ -89,8 +88,8 @@ async function makeJnxFromLayer(srcLayer, layerName, maxZoomLevel, latLngBounds,
let imageRec;
try {
imageRec = await tilePromise;
- } catch (xhr) {
- error = new Error(formatXhrError(xhr, 'map tile'));
+ } catch (e) {
+ error = e;
doStop = true;
break;
}
diff --git a/src/lib/leaflet.control.layers.configure/index.js b/src/lib/leaflet.control.layers.configure/index.js
@@ -3,6 +3,7 @@ import './style.css';
import enableTopRow from 'lib/leaflet.control.layers.top-row';
import ko from 'vendored/knockout';
import {notify} from 'lib/notifications';
+import logging from 'lib/logging';
function enableConfig(control, layers) {
@@ -46,6 +47,9 @@ function enableConfig(control, layers) {
try {
storedLayersEnabled = JSON.parse(serialized);
} catch (e) {
+ logging.captureMessage('Failed to load enabled layers from localstorage - invalid json',{
+ extra: {"localstorage.layersEnabled": serialized.slice(0, 1000)}
+ })
}
}
}
diff --git a/src/lib/leaflet.control.printPages/control.js b/src/lib/leaflet.control.printPages/control.js
@@ -7,12 +7,13 @@ import PageFeature from './pageFeature';
import Contextmenu from 'lib/contextmenu';
import {renderPages} from './map-render'
import formHtml from './form.html';
-import {notify, notifyXhrError} from 'lib/notifications';
+import {notify} from 'lib/notifications';
import {makePdf} from './pdf';
import {saveAs} from 'browser-filesaver';
import {blobFromString} from 'lib/binary-strings';
import 'lib/leaflet.hashState/leaflet.hashState';
import 'lib/leaflet.control.commons';
+import logging from 'lib/logging';
ko.extenders.checkNumberRange = function(target, range) {
return ko.pureComputed({
@@ -179,6 +180,7 @@ L.Control.PrintPages = L.Control.extend({
},
savePdf: function() {
+ logging.captureBreadcrumbWithUrl({message: 'start save pdf'});
if (!this._map) {
return;
}
@@ -207,16 +209,14 @@ L.Control.PrintPages = L.Control.extend({
}
}
).catch((e) => {
- if (e.status !== undefined) {
- notifyXhrError(e, 'map tile');
- } else {
- notify(e);
- }
+ logging.captureException(e);
+ notify(`Failed to create PDF: ${e.message}`);
}
).then(() => this.makingPdf(false));
},
savePageJpg: function(page) {
+ logging.captureBreadcrumbWithUrl({message: 'start save page jpg', data: {pageNumber: page.getLabel()}});
const pages = [{
latLngBounds: page.getLatLngBounds(),
printSize: page.getPrintSize()
@@ -235,12 +235,8 @@ L.Control.PrintPages = L.Control.extend({
)
.then((images) => savePageJpg(images[0]))
.catch((e) => {
- // throw e;
- if (e.status !== undefined) {
- notifyXhrError(e, 'map');
- } else {
- notify(e);
- }
+ logging.captureException(e);
+ notify(`Failed to create JPEG from page: ${e.message}`);
}
).then(() => this.makingPdf(false));
},
diff --git a/src/lib/leaflet.control.printPages/pageFeature.js b/src/lib/leaflet.control.printPages/pageFeature.js
@@ -72,6 +72,10 @@ const PageFeature = L.Marker.extend({
this._icon.innerHTML = s;
},
+ getLabel: function() {
+ return this._icon.innerHTML;
+ },
+
setSize: function(paperSize, scale) {
this.paperSize = paperSize;
this.scale = scale;
diff --git a/src/lib/leaflet.control.track-list/track-list.js b/src/lib/leaflet.control.track-list/track-list.js
@@ -19,7 +19,8 @@ import 'lib/leaflet.control.commons';
import {blobFromString} from 'lib/binary-strings';
import 'lib/leaflet.polyline-edit';
import 'lib/leaflet.polyline-measure';
-
+import logging from 'lib/logging';
+import {notify} from 'lib/notifications';
const TrackSegment = L.MeasuredLine.extend({
@@ -214,11 +215,13 @@ L.Control.TrackList = L.Control.extend({
},
loadFilesFromDisk: function() {
+ logging.captureBreadcrumb({message: 'load track from disk'});
selectFiles(true).then(this.loadFilesFromFilesObject.bind(this));
},
loadFilesFromUrl: function() {
var url = this.url().trim();
+ logging.captureBreadcrumb({message: 'load track from url', data: {url: url}});
try {
url = decodeURIComponent(url);
} catch (e) {
@@ -293,7 +296,8 @@ L.Control.TrackList = L.Control.extend({
);
this.readingFiles(false);
if (messages.length) {
- alert(messages.join('\n'));
+ logging.captureMessage('errors in loaded tracks', {extra: {message: messages.join('\n')}});
+ notify(messages.join('\n'));
}
},
@@ -483,7 +487,7 @@ L.Control.TrackList = L.Control.extend({
}
if (lines.length === 0 && points.length === 0) {
- alert('Track is empty, nothing to save');
+ notify('Track is empty, nothing to save');
return;
}
diff --git a/src/lib/leaflet.hashState/leaflet.hashState.js b/src/lib/leaflet.hashState/leaflet.hashState.js
@@ -1,5 +1,6 @@
import L from 'leaflet';
import hashState from './hashState';
+import logging from 'lib/logging';
L.Mixin.HashState = {
enableHashState: function(key, defaultInitialState = null) {
@@ -38,6 +39,7 @@ L.Mixin.HashState = {
_onExternalStateChanged: function(state) {
this._ignoreStateChange = true;
if (!this.unserializeState(state)) { // state from hash is invalid, update hash from component state
+ logging.captureMessageWithUrl(`Invalid state in hash string (key "${this._hashStateKey}")`);
hashState.updateState(this._hashStateKey, this.serializeState());
}
this._ignoreStateChange = false;
diff --git a/src/lib/leaflet.layer.geojson-ajax/index.js b/src/lib/leaflet.layer.geojson-ajax/index.js
@@ -1,6 +1,7 @@
import L from 'leaflet';
import {fetch} from 'lib/xhr-promise';
-import {notifyXhrError} from 'lib/notifications';
+import {notify} from 'lib/notifications';
+import logging from 'lib/logging';
L.Layer.GeoJSONAjax = L.GeoJSON.extend({
options: {
@@ -20,7 +21,14 @@ L.Layer.GeoJSONAjax = L.GeoJSON.extend({
fetch(this.url, {responseType: 'json', timeout: this.options.requestTimeout})
.then(
(xhr) => this.addData(xhr.response),
- (xhr) => notifyXhrError(xhr, `GeoJSON data from ${this.url}`)
+ (e) => {
+ logging.captureException(e, {extra: {
+ description: 'failed to get geojson',
+ url: this.url,
+ status: e.xhr.status
+ }});
+ notify(`Failed to get GeoJSON data from ${this.url}: ${e.message}`);
+ }
)
},
diff --git a/src/lib/leaflet.layer.nordeskart/index.js b/src/lib/leaflet.layer.nordeskart/index.js
@@ -1,14 +1,11 @@
import L from 'leaflet';
import {fetch} from 'lib/xhr-promise';
-import {formatXhrError, notify} from 'lib/notifications';
+import {notify} from 'lib/notifications';
+import logging from 'lib/logging';
function parseResponse(s) {
let data;
- try {
- data = JSON.parse(s);
- } catch (e) {
- throw new Error('invalid JSON');
- }
+ data = JSON.parse(s);
if (!data.token) {
throw new Error('no token in response');
}
@@ -21,12 +18,15 @@ function getToken() {
try {
return {token: parseResponse(xhr.responseText)}
} catch (e) {
- console.log(e);
+ logging.captureException(e, {extra: {
+ description: 'Invalid baat token',
+ response: xhr.responseText.toString().slice(0, 100)}});
return {error: 'Server returned invalid token for Norway map'}
}
},
- function(xhr) {
- return {error: formatXhrError(xhr, 'token for Norway map')}
+ function(e) {
+ logging.captureException(e, {extra: {description: 'failed to download baat token'}});
+ return {error: `Failed to token for Norway map: ${e.message}`};
}
);
}
diff --git a/src/lib/leaflet.layer.westraPasses/westraPassesMarkers.js b/src/lib/leaflet.layer.westraPasses/westraPassesMarkers.js
@@ -5,8 +5,8 @@ import escapeHtml from 'escape-html';
import {saveAs} from 'browser-filesaver';
import iconFromBackgroundImage from 'lib/iconFromBackgroundImage';
import {fetch} from 'lib/xhr-promise';
-import {notifyXhrError} from 'lib/notifications';
-
+import {notify} from 'lib/notifications';
+import logging from 'lib/logging';
const WestraPassesMarkers = L.Layer.CanvasMarkers.extend({
options: {
@@ -29,9 +29,19 @@ const WestraPassesMarkers = L.Layer.CanvasMarkers.extend({
fetch(this.url, {responseType: 'json'})
.then(
(xhr) => this._loadMarkers(xhr),
- (xhr) => notifyXhrError(xhr, 'westra passes data')
+ (e) => {
+ this._downloadStarted = false;
+ logging.captureException(e, {
+ extra: {
+ description: 'failed to get westra passes',
+ url: this.url,
+ status: e.xhr.status
+ }
+ }
+ );
+ notify('Failed to get Westra passes data');
+ }
);
-
},
onAdd: function(map) {
diff --git a/src/lib/logging/index.js b/src/lib/logging/index.js
@@ -1,15 +1,30 @@
import Raven from 'raven-js';
-function captureException(e) {
- Raven.captureException(e)
+function captureException(e, options) {
+ Raven.captureException(e, options)
}
-function captureMessage(msg) {
- Raven.captureMessage(msg)
+function captureMessage(msg, options) {
+ Raven.captureMessage(msg, options)
+}
+
+function captureMessageWithUrl(msg) {
+ captureMessage(msg, {extra: {url: window.location.toString()}});
}
function setExtraContext(data) {
Raven.setExtraContext(data)
}
-export default {captureMessage, captureException, setExtraContext}
-\ No newline at end of file
+function captureBreadcrumb(crumb) {
+ Raven.captureBreadcrumb(crumb)
+}
+
+function captureBreadcrumbWithUrl(crumb) {
+ const data = Object.assign(crumb.data || {}, {'url': window.location.toString()});
+ crumb = Object.assign({}, crumb, {data});
+ captureBreadcrumb(crumb);
+
+}
+
+export default {captureMessage, captureException, setExtraContext, captureBreadcrumbWithUrl, captureBreadcrumb, captureMessageWithUrl}
+\ No newline at end of file
diff --git a/src/lib/notifications/index.js b/src/lib/notifications/index.js
@@ -6,15 +6,4 @@ function prompt(message, value) {
return window.prompt(message, value);
}
-function formatXhrError(xhr, whatWasDownloading) {
- const reason = xhr.status === 0 ? 'network error' : `server response is ${xhr.status}`;
- const message = `Failed to download ${whatWasDownloading}: ${reason}`;
- return message;
-}
-
-function notifyXhrError(xhr, whatWasDownloading, level) {
- const message = formatXhrError(xhr, whatWasDownloading);
- notify(message, level);
-}
-
-export {notify, prompt, notifyXhrError, formatXhrError};
-\ No newline at end of file
+export {notify, prompt};
+\ No newline at end of file
diff --git a/src/lib/xhr-promise/index.js b/src/lib/xhr-promise/index.js
@@ -8,6 +8,16 @@ function retryIfNetworkErrorOrServerError(xhr) {
return (xhr.status === 0 || xhr.status >= 500);
}
+class XMLHttpRequestPromiseError extends Error {
+ constructor(xhr) {
+ super();
+ this.xhr = xhr;
+ this.name = 'XMLHttpRequestPromiseError';
+
+ this.message = xhr.status === 0 ? 'network error' : `server response is ${xhr.status}`;
+ }
+}
+
class XMLHttpRequestPromise {
constructor(
url, {method='GET', data=null, responseType='', timeout=30000, maxTries=3, retryTimeWait=1000,
@@ -62,7 +72,7 @@ class XMLHttpRequestPromise {
this._timerId = setTimeout(() => this.send(), this._retryTimeWait);
} else {
// console.log('failed', this.url);
- this._reject(xhr);
+ this._reject(new XMLHttpRequestPromiseError(xhr));
}
}
}