nakarte

Source code of https://map.sikmir.ru (fork)
git clone git://git.sikmir.ru/nakarte
Log | Files | Refs | LICENSE

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:
Msrc/lib/clipboardCopy/index.js | 2++
Msrc/lib/leaflet.control.elevation-profile/index.js | 10+++++-----
Msrc/lib/leaflet.control.jnx/index.js | 8+++++++-
Msrc/lib/leaflet.control.jnx/jnx-maker.js | 5++---
Msrc/lib/leaflet.control.layers.configure/index.js | 4++++
Msrc/lib/leaflet.control.printPages/control.js | 20++++++++------------
Msrc/lib/leaflet.control.printPages/pageFeature.js | 4++++
Msrc/lib/leaflet.control.track-list/track-list.js | 10+++++++---
Msrc/lib/leaflet.hashState/leaflet.hashState.js | 2++
Msrc/lib/leaflet.layer.geojson-ajax/index.js | 12++++++++++--
Msrc/lib/leaflet.layer.nordeskart/index.js | 18+++++++++---------
Msrc/lib/leaflet.layer.westraPasses/westraPassesMarkers.js | 18++++++++++++++----
Msrc/lib/logging/index.js | 27+++++++++++++++++++++------
Msrc/lib/notifications/index.js | 15++-------------
Msrc/lib/xhr-promise/index.js | 12+++++++++++-
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)); } } }