From 4138552f0b340cd551ce7b8a1560280858541deb Mon Sep 17 00:00:00 2001 From: PEM-- Date: Mon, 17 Aug 2015 12:39:31 +0200 Subject: [PATCH 1/3] Admin template override - Jade to Blaze, CoffeeScript to Javascript --- tutorials/admin-template-override.md | 171 +++++++++++++++------------ 1 file changed, 96 insertions(+), 75 deletions(-) diff --git a/tutorials/admin-template-override.md b/tutorials/admin-template-override.md index 0ec1b81..ff6bbd5 100644 --- a/tutorials/admin-template-override.md +++ b/tutorials/admin-template-override.md @@ -13,85 +13,106 @@ Our collection is named `subscribers` and we are going to extend its index. We start by creating a template file that displays the usual title, a tabular table and an additional button used for the CSV export. -```jade -// File: subscribersIndex.jade - -template(name='subscribersIndex') - // subscribersIndex - +Layout template='orionCpTitle' - | {{ collection.title }} - if collection.canShowCreate - a(href="{{ collection.createPath }}"): i.fa.fa-plus - +Layout template='orionCpContentContainer' - if collection.canIndex - if showTable - +tabular table=collection.tabularTable class='table index-table' - // This is our additional button - button.import-csv CSV export +```html + + + ``` Now we set the logic in: -```coffee -# File: subscribersIndex.coffee - -# Here we ask Orion to replace the basic index for the subscriber with our -# freshly created new template subscribersIndex. This addition is done -# client side and server side. -ReactiveTemplates.set 'collections.subscribers.index', 'subscribersIndex' - -# Now, client side, we set the default logic. -if Meteor.isClient - Template.subscribersIndex.onCreated -> - appLog.info 'subscribersIndex created' - @subscribersIndex_showTable = new ReactiveVar - @subscribersIndex_showTable.set false - - Template.subscribersIndex.onRendered -> - @autorun => - Template.currentData() - @subscribersIndex_showTable.set false - Meteor.defer => - @subscribersIndex_showTable.set true - - Template.subscribersIndex.helpers - showTable: -> - Template.instance().subscribersIndex_showTable.get() - - Template.subscribersIndex.events - 'click tr': (e, t) -> - return unless $(event.target).is 'td' - collection = Template.currentData().collection - dataTable = $(e.target).closest('table').DataTable() - rowData = dataTable.row(e.currentTarget).data() - if rowData?.canShowUpdate() - path = collection.updatePath rowData - RouterLayer.go path - - # Here we create the behavior for the new button - 'click button.import-csv': (e, t) -> - # Prevent further actions - csvButton = t.$ 'button.import-csv' - csvButton.addClass 'disabled' - subscription = Meteor.subscribe 'allSubscribers', - onReady: -> - data = Subscribers.find({},{name: 1, forname: 1, _id: 0}).fetch() - # Create the header - csv = (_.keys data[0]).join ';' - for sub in data - csv += '\n' + (_.values sub).join ';' - # Automatic download of the CSV as a Blob file - blobDownload csv, 'subscribers.csv', 'text/csv' - # Allow further extracts - csvButton.removeClass 'disabled' - onError: (err) -> - sAlert.warning 'CSV subscription failed' - appLog.warn 'CSV subscription failed', err - # Allow further extracts - csvButton.removeClass 'disabled' - -if Meteor.isServer - Meteor.publish 'allSubscribers', -> Subscribers.find() +```js +ReactiveTemplates.set('collections.subscribers.index', 'subscribersIndex'); + +if (Meteor.isClient) { + Template.subscribersIndex.onCreated(function() { + appLog.info('subscribersIndex created'); + this.subscribersIndex_showTable = new ReactiveVar; + return this.subscribersIndex_showTable.set(false); + }); + Template.subscribersIndex.onRendered(function() { + return this.autorun((function(_this) { + return function() { + Template.currentData(); + _this.subscribersIndex_showTable.set(false); + return Meteor.defer(function() { + return _this.subscribersIndex_showTable.set(true); + }); + }; + })(this)); + }); + Template.subscribersIndex.helpers({ + showTable: function() { + return Template.instance().subscribersIndex_showTable.get(); + } + }); + Template.subscribersIndex.events({ + 'click tr': function(e, t) { + var collection, dataTable, path, rowData; + if (!$(event.target).is('td')) { + return; + } + collection = Template.currentData().collection; + dataTable = $(e.target).closest('table').DataTable(); + rowData = dataTable.row(e.currentTarget).data(); + if (rowData != null ? rowData.canShowUpdate() : void 0) { + path = collection.updatePath(rowData); + return RouterLayer.go(path); + } + }, + 'click button.import-csv': function(e, t) { + var csvButton, subscription; + csvButton = t.$('button.import-csv'); + csvButton.addClass('disabled'); + return subscription = Meteor.subscribe('allSubscribers', { + onReady: function() { + var csv, data, i, len, sub; + data = Subscribers.find({}, { + name: 1, + forname: 1, + _id: 0 + }).fetch(); + csv = (_.keys(data[0])).join(';'); + for (i = 0, len = data.length; i < len; i++) { + sub = data[i]; + csv += '\n' + (_.values(sub)).join(';'); + } + blobDownload(csv, 'subscribers.csv', 'text/csv'); + return csvButton.removeClass('disabled'); + }, + onError: function(err) { + sAlert.warning('CSV subscription failed'); + appLog.warn('CSV subscription failed', err); + return csvButton.removeClass('disabled'); + } + }); + } + }); +} + +if (Meteor.isServer) { + Meteor.publish('allSubscribers', function() { + return Subscribers.find(); + }); +} ``` ### Tip From e0df3e861d6424b0ee62f005cd0d7fe911f39dd7 Mon Sep 17 00:00:00 2001 From: PEM-- Date: Mon, 17 Aug 2015 15:23:43 +0200 Subject: [PATCH 2/3] Logging system and custom log formatter --- tutorials.json | 10 ++ tutorials/logging-system.md | 194 ++++++++++++++++++++++++++++++++++++ 2 files changed, 204 insertions(+) create mode 100644 tutorials/logging-system.md diff --git a/tutorials.json b/tutorials.json index 56397fe..7fd4232 100644 --- a/tutorials.json +++ b/tutorials.json @@ -48,5 +48,15 @@ "file": "admin-template-override.md", "smallImage": "http://apod.nasa.gov/apod/image/1308/arp271_gemini_2048.jpg", "bigImage": "http://apod.nasa.gov/apod/image/1308/arp271_gemini_2048.jpg" + }, + { + "slug": "logging-system", + "title": "Logging system", + "description": "Create your own logger or re-use Orion's one", + "author": "Pierre-Eric Marchandet", + "authorGithub": "PEM--", + "file": "logging-system", + "smallImage": "https://s-media-cache-ak0.pinimg.com/originals/c5/68/46/c56846d983440c26fe430727d25f76b8.jpg", + "bigImage": "https://s-media-cache-ak0.pinimg.com/originals/c5/68/46/c56846d983440c26fe430727d25f76b8.jpg" } ] diff --git a/tutorials/logging-system.md b/tutorials/logging-system.md new file mode 100644 index 0000000..2e58fdc --- /dev/null +++ b/tutorials/logging-system.md @@ -0,0 +1,194 @@ +## Logging system +### Introduction +Orion provides its own logging system based on [Bunyan](https://github.com/trentm/node-bunyan). +Isomorphic, it allows you to use the same API wether you want to log information +on the client side or on the server side. + +### Simple use +Though we recommend creating your own logger to discriminate your logs from the +one of Orion, you can easily use the default logger just like the [Console API](https://developer.chrome.com/devtools/docs/console-api): + +* **trace**: Use it for tracking intermediary state information. + ```js + orion.log.trace('Simple trace level message', {value: true}, false); + ``` +* **debug**: Use it for debugging. + ```js + orion.log.debug('Simple debug level message', {value: true}, false); + ``` +* **info**: Use it for tracking relevant state or status information. + ```js + orion.log.info('Simple info level message', {value: true}, false); + ``` +* **warn**: Use it for warning when something goes wrong but is still properly managed by your application. + ```js + orion.log.warn('Simple warning level message', {value: true}, false); + ``` +* **error**: Use it for errors when something goes wrong and you've no certainty that you can keep a proper application behavior. + ```js + orion.log.error('Simple error level message', {value: true}, false); + ``` +* **fatal**: Use it for fatal errors when the situation cannot be recovered with a full reboot. + ```js + orion.log.fatal('Simple fatal level message', {value: true}, false); + ``` + +### Setting the appropriate log level +You can remove set the log level using [Bunyan](https://github.com/trentm/node-bunyan)'s API. Here are some useful commands: +#### Remove all logs on Orion's default logger +```js +orion.log.level('none'); +``` +#### Set level for the Orion's default logger +```js +// For debug level and above +orion.log.level('debug'); +... +// For info level and above +orion.log.level('info'); +... +// For fatal only level +orion.log.level('fatal'); +``` + +### Create your own logger +We recommend that you create your own logging system to discriminate your logs +from the ones of Orion so that it gets easier for you to check wether the issue +is within your application or caused by a misuse or an issue of Orion. + +Within you application, just create a Javascript file that it sufficiently +prioritized so that you logger is available as soon as possible (see [Meteor's File Load Order](http://docs.meteor.com/#/full/structuringyourapp)). +A file named `lib/utils/priority/_logger.js` should be sufficiently prioritized. + +As well as a default logger, Orion provides its default log formatter `orion.logFormatter` that you can +rely on for a simple logging strategy. Here's how to create your logger with the +default formatter and a default log level set on **info**: +```js +// Client and server side +myAppLog = new bunyan.createLogger({ + name: 'myApp', + stream: orion.logFormatter, + level: 'info' +}); +``` + +Now wether you are client side or server side, you can use your logger like so: +```js +myAppLog.info('Simple info level message', myVar, 'any text', anyOtherObjectOrVar); +myAppLog.warn('Simple warning level message', myVar, 'any text', anyOtherObjectOrVar); +myAppLog.error('Simple error level message', myVar, 'any text', anyOtherObjectOrVar); +... +``` + +### Create you own formatter +Log formatter are the real power of [Bunyan](https://github.com/trentm/node-bunyan). +This is where you can implement all your logging strategies like: + +* Sending your server's logs to a [logstash](https://www.elastic.co/products/logstash) server. +* Send an email when a specific log level has been executed. +* Sending your client's logs and your server's logs to a SaaS + (ex. [Loggly](https://www.loggly.com/), [LogEntries](https://logentries.com), ...). +* Enabling logging strategy for a specific connected customer so that you can + see what's this user has done within your application. +* ... + +While being completely isomorphic in its API, the log formatter has to be written +differently depending on its execution context, client or server side as we depict +it afterwards. Both implementation must leverage the power of [Node's Stream](https://nodejs.org/api/stream.html). + +> Thanks to [Browserify](http://browserify.org/) and [Cosmo's meteor package for browserify](https://github.com/elidoran/cosmos-browserify), + the [Node's Stream](https://nodejs.org/api/stream.html) is available on client side making it isomorphic as well. + +#### Client side log formatter +For this part, we are simply using the basic [Console API](https://developer.chrome.com/devtools/docs/console-api) +along with some colored styles thanks to [Log with style](https://www.npmjs.com/package/log-with-style) +which is also exposed by Orion. + +On client side, Orion's log system exposes the following API: +* `bunyan`: [Bunyan](https://github.com/trentm/node-bunyan). +* `process`: [Browserified Node's process](https://www.npmjs.com/package/process). +* `WritableStream`: [Browserified Node's WritableStream](https://nodejs.org/api/stream.html#stream_class_stream_writable). +* `inherits`: [Browserified Node utils's inherits](https://nodejs.org/docs/latest/api/util.html#util_util_inherits_constructor_superconstructor). +* `logStyle`: [Log with style](https://www.npmjs.com/package/log-with-style) a simple set of colors and styles for [Console API](https://developer.chrome.com/devtools/docs/console-api). + +One of the major advantage of using [Bunyan](https://github.com/trentm/node-bunyan) +is that all log informations are treated as streams of JSON objects. In this +formatter example we are using this capabilities to pick the appropriate value +that we want to display in your DevTools console. + +```js +if (Meteor.isClient) { + inherits(BrowserStdout, WritableStream); + + function BrowserStdout() { + if (!(this instanceof BrowserStdout)) { + return new BrowserStdout(); + } + WritableStream.call(this); + } + + BrowserStdout.prototype._write = function(chunks, encoding, cb) { + var output = JSON.parse(chunks.toString ? chunks.toString() : chunks); + var color = '[c="color: green"]'; + var level = 'INFO'; + if (output.level > 40) { + color = '[c="color: red"]'; + if (output.level === 60) { + level = 'FATAL'; + } else { + level = 'ERROR'; + } + } else if (output.level === 40) { + color = '[c="color: orange"]'; + level = 'WARNING'; + } else if (output.level === 20) { + level = 'DEBUG'; + } else if (output.level === 10) { + level = 'TRACE'; + } + logStyle(color + level + '[c] ' + '[c="color: blue"]' + output.name + '[c] ' + output.msg); + process.nextTick(cb); + }; + + myLogFormatter = BrowserStdout(); +} +``` +Now we have our custom log formatter client side named `myLogFormatter`. + +#### Server side log formatter +For this part, we are using an already made formatter available in the NPM registry: [Bunyan Format](https://www.npmjs.com/package/bunyan-format). + +On server side, Orion's log system exposes the following API: +* `bunyan`: [Bunyan](https://github.com/trentm/node-bunyan). +* `bunyanFormat`: [Bunyan Format](https://www.npmjs.com/package/bunyan-format). + +```js +if (Meteor.isServer) { + myLogFormatter = bunyanFormat({outputMode: 'short', color: true}); +} +``` + +Super easy. Of course, you can go pretty far using [Node's Stream](https://nodejs.org/api/stream.html) +and implement every use cases that would suit your logging strategies. + +#### Using our custom formatter +Now that we have implemented our custom logger with the same name on the client +side as well as the server side, we can go back to isomorphic Javascript and +customize our logger with our shared `myLogFormatter`: + +```js +// Client and server side +myAppLog = new bunyan.createLogger({ + name: 'myApp', + stream: myLogFormatter +}); +``` + +### Links +* Inspired from [Ongoworks's Bunyan](https://github.com/ongoworks/meteor-bunyan) +* [Bunyan](https://github.com/trentm/node-bunyan) +* [Bunyan Format](https://www.npmjs.com/package/bunyan-format) +* [Comparison between Winston and Bunyan](https://strongloop.com/strongblog/compare-node-js-logging-winston-bunyan/) +* [Log with style](https://www.npmjs.com/package/log-with-style) +* [Node's Stream](https://nodejs.org/api/stream.html) +* [Handbook for Node's Stream](https://github.com/substack/stream-handbook) From 8da9e51f88cabeb8c91f5a76836e94b4fda7443a Mon Sep 17 00:00:00 2001 From: PEM-- Date: Mon, 17 Aug 2015 15:30:38 +0200 Subject: [PATCH 3/3] Logging system and custom log formatter --- tutorials.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorials.json b/tutorials.json index 7fd4232..9e54e3e 100644 --- a/tutorials.json +++ b/tutorials.json @@ -55,7 +55,7 @@ "description": "Create your own logger or re-use Orion's one", "author": "Pierre-Eric Marchandet", "authorGithub": "PEM--", - "file": "logging-system", + "file": "logging-system.md", "smallImage": "https://s-media-cache-ak0.pinimg.com/originals/c5/68/46/c56846d983440c26fe430727d25f76b8.jpg", "bigImage": "https://s-media-cache-ak0.pinimg.com/originals/c5/68/46/c56846d983440c26fe430727d25f76b8.jpg" }