pax_global_header00006660000000000000000000000064137761656440014535gustar00rootroot0000000000000052 comment=0ff1ae29cc9e028c6c11cd6b60e3b90217b66a10 node-bunyan-2.0.5/000077500000000000000000000000001377616564400137605ustar00rootroot00000000000000node-bunyan-2.0.5/.github/000077500000000000000000000000001377616564400153205ustar00rootroot00000000000000node-bunyan-2.0.5/.github/workflows/000077500000000000000000000000001377616564400173555ustar00rootroot00000000000000node-bunyan-2.0.5/.github/workflows/check.yml000066400000000000000000000003751377616564400211620ustar00rootroot00000000000000name: Check Lint & Style on: [push, pull_request] jobs: check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: node-version: '12.x' - run: npm ci - run: npm run check node-bunyan-2.0.5/.github/workflows/test.yml000066400000000000000000000025261377616564400210640ustar00rootroot00000000000000name: Test on: push: paths-ignore: - 'docs/**' - '*.md' pull_request: paths-ignore: - 'docs/**' - '*.md' jobs: # Test once on every (available) plat, using LTS node version # (https://nodejs.org/en/about/releases/). test-plats: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: node-version: '12.x' - run: npm ci - run: npm test # Test once for every supported node version (don't repeat the LTS # node version from the previous step). Only test on one # platform to not overkill the number of builds. test-vers: strategy: matrix: node: ['8.x', '10.x', '14.x'] runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: node-version: ${{ matrix.node }} - run: npm ci - run: npm test # Test older versions separately because really old node/npm don't support # 'npm ci'. test-old-vers: strategy: matrix: node: ['0.10.x', '4.x', '6.x'] runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: node-version: ${{ matrix.node }} - run: npm install - run: npm test node-bunyan-2.0.5/.gitignore000066400000000000000000000001151377616564400157450ustar00rootroot00000000000000/tmp /node_modules *.log !/test/corpus/*.log /*.tgz /test/log.test.rot.log.* node-bunyan-2.0.5/.npmignore000066400000000000000000000000671377616564400157620ustar00rootroot00000000000000/tmp /node_modules *.log /examples /test /*.tgz /tools node-bunyan-2.0.5/AUTHORS000066400000000000000000000034441377616564400150350ustar00rootroot00000000000000Trent Mick (http://trentm.com) Mark Cavage (https://github.com/mcavage) Dave Pacheco (https://github.com/davepacheco) Michael Hart (https://github.com/mhart) Isaac Schlueter (https://github.com/isaacs) Rob Gulewich (https://github.com/rgulewich) Bryan Cantrill (https://github.com/bcantrill) Michael Hart (https://github.com/mhart) Simon Wade (https://github.com/aexmachina) https://github.com/glenn-murray-bse Chakrit Wichian (https://github.com/chakrit) Patrick Mooney (https://github.com/pfmooney) Johan Nordberg (https://github.com/jnordberg) https://github.com/timborodin Ryan Graham (https://github.com/rmg) Alex Kocharin (https://github.com/rlidwka) Andrei Neculau (https://github.com/andreineculau) Mihai Tomescu (https://github.com/matomesc) Daniel Juhl (https://github.com/danieljuhl) Chris Barber (https://github.com/cb1kenobi) Manuel Schneider (https://github.com/manuelschneider) Martin Gausby (https://github.com/gausby) Stéphan Kochen (https://github.com/stephank) Shakeel Mohamed (https://github.com/shakeelmohamed) Denis Izmaylov (https://github.com/DenisIzmaylov) Guillermo Grau Panea (https://github.com/guigrpa) Mark LeMerise (https://github.com/MarkLeMerise) https://github.com/sometimesalready Charly Koza (https://github.com/Cactusbone) Thomas Heymann (https://github.com/cyberthom) David M. Lee (https://github.com/leedm777) Marc Udoff (https://github.com/mlucool) Mark Stosberg (https://github.com/markstos) Alexander Ray (https://github.com/aray12) Adam Lynch (https://github.com/adam-lynch) Michael Nisi (https://github.com/michaelnisi) Martijn Schrage (https://github.com/Oblosys) Paul Milham (https://github.com/domrein) Frankie O'Rourke (https://github.com/psfrankie) Cody Mello (https://github.com/melloc) Todd Whiteman (https://github.com/twhiteman) Zach Bjornson (https://github.com/zbjornson) node-bunyan-2.0.5/CHANGES.md000066400000000000000000001341401377616564400153550ustar00rootroot00000000000000# bunyan Changelog See [the bunyan@1.x changelog](https://github.com/trentm/node-bunyan/blob/1.x/CHANGES.md) for details on recent 1.x releases. Known issues: - [issue #58] Can't install to a dir with spaces. This is [this node-gyp bug](https://github.com/TooTallNate/node-gyp/issues/65). ## not yet released (nothing yet) ## 2.0.5 (beta) - [pull #575, #278] Change the default "req" serializer to accept expressjs's `req.originalUrl` for the "url" field per . (By @twelve17 and @kingcody.) - Development change: Switch to node-tap for testing (from nodeunit, which is now obsolete). Currently just tap v9 because that is the last major version of node-tap that supports back to node v0.10. ## 2.0.4 (beta) - [pull #558] Update minimum "moment" version to 2.19.3 for CVE-2017-18214. - [issue #589] Use `os.EOL` for newlines in bunyan output, which helps with some Unix-EOL-naive apps like notepad. (By @bwknight877.) - Development change: Switched to GitHub Actions for CI. ## 2.0.3 (beta) - Fix a vulnerability from a crafted argument to 'bunyan -p ARG' This was reported privately as: https://hackerone.com/reports/902739 bunyan - RCE via insecure command formatting Previous to this version the 'bunyan' CLI was not escaping a given argument to the '-p' option before executing `ps -A -o pid,command | grep '$ARG'` which could lead to unintended execution. (This same change is also in bunyan@1.8.3.) ## 2.0.2 (beta) - [issue #444] Fix the `bunyan` CLI to not duplicate the "HTTP/1.1 ..." status line when serializing a "res" field. ## 2.0.1 (beta) - [issue #504] **Backward incompatible change to the `bunyan` CLI:** The `bunyan` CLI no longer adds a `Host` header when rendering a `client_req` field in a log record. In 1.x it used to do this (using `client_req.address` and `client_req.port`), on the *guess* that Node.js' `http.ClientRequest` handling would add it. However, the guess can be wrong and misleading. It is better not to guess. ## 2.0.0 (beta) - [issue #499] Fix some `bunyan` CLI exit handling problems. ## 1.8.10 - Ensure that `bunyan` errors out if attempting to use `-p PID` and file args at the same time. ## 1.8.9 - [pull #409, issue #246] Revert a change added to the `bunyan` CLI version 1.0.1 where `SIGINT` was ignored, such that Ctrl+C could not be used to terminate bunyan. (By @zbjornson and @davepacheco.) - [pull #469] Fix a strict mode (`"use strict;"`) error in some versions of Safari. ## 1.8.8 - Fix breakage due to a silly last minute "fix 'make check'". ## 1.8.7 Note: *Bad release.* Use 1.8.8 or later. - [issue #484] Fix breakage due to #474 in previous release. ## 1.8.6 Note: *Bad release.* Use 1.8.7 or later. - [issue #474] Bunyan's `safeCycles` is too slow when logging large objects. ## 1.8.5 - [issue #401] Improved performance when using disabled log levels. ## 1.8.4 - [issue #454] Fix `src` usage with node v7. ## 1.8.3 - [issue #450] Fix `log.info(null)` crash that resulted from #426 in v1.8.2. ## 1.8.2 - [issue #449] Bump dtrace-provider dep to 0.7.0 to help avoid deprecation warnings with node v6 in some cases. - [issue #426] Ensure `log.info({err: err})` results in a "msg" value, just like `log.info(err)`. ## 1.8.1 - [pull #386] Fix bad bug in rotation that could cause a crash with error message "cannot start a rotation when already rotating" (by Frankie O'Rourke). The bug was introduced in 1.8.0. ## 1.8.0 Note: *Bad release.* An addition in this release broke 'rotating-file' usage. Use 1.8.1 or later. - [issue #370] Fix `bunyan -p ...` (i.e. DTrace integration) on node 4.x and 5.x. - [issue #329, pull #330] Update the 'rotating-file' stream to do a file rotation on initialization if the mtime on the file path indicates the last rotation time was missed -- i.e. if the app wasn't running at the time. (by Paul Milham.) ## 1.7.1 - [issue #332, pull #355] Ensure stream for type='stream' stream is a writable stream. (By Michael Nisi.) - [issue #344] Fix "rotating-file" Bunyan streams to not miss rotations when configured for a period greater than approximately 25 days. Before this there was an issue where periods greater than node.js's maximum `setTimeout` length would fail to rotate. (By Martijn Schrage.) - [issue #234, pull #345] Improve `bunyan` CLI rendering of "res" field HTTP responses to not show two blank lines for an empty body. (By Michael Nisi.) ## 1.7.0 - [pull #311, #302, #310] Improve the runtime environment detection to fix running under [NW.js](http://nwjs.io/). Contributions by Adam Lynch, Jeremy Ruppel, and Aleksey Timchenko. - [pull #318] Add `reemitErrorEvents` optional boolean for streams added to a Bunyan logger to control whether an "error" event on the stream will be re-emitted on the `Logger` instance. var log = bunyan.createLogger({ name: 'foo', streams: [ { type: 'raw', stream: new MyCustomStream(), reemitErrorEvents: true } ] }); Before this change, "error" events were re-emitted on [`file` streams](https://github.com/trentm/node-bunyan#stream-type-file) only. The new behaviour is as follows: - `reemitErrorEvents` not specified: `file` streams will re-emit error events on the Logger instance. - `reemitErrorEvents: true`: error events will be re-emitted on the Logger for any stream with a `.on()` function -- which includes file streams, process.stdout/stderr, and any object that inherits from EventEmitter. - `reemitErrorEvents: false`: error events will not be re-emitted for any streams. Dev Note: Bunyan `Logger` objects don't currently have a `.close()` method in which registered error event handlers can be *un*registered. That means that a (presumably rare) situation where code adds dozens of Bunyan Logger streams to, e.g. process.stdout, and with `reemitErrorEvents: true`, could result in leaking Logger objects. Original work for allowing "error" re-emitting on non-file streams is by Marc Udoff in pull #318. ## 1.6.0 - [pull #304, issue #245] Use [Moment.js][momentjs.com] library to handle `bunyan` CLI time formatting in some cases, especially to fix display of local time. It is now required for local time formatting (i.e. `bunyan -L` or `bunyan --time local`). (By David M. Lee.) - [pull #252] Fix errant `client_res={}` in `bunyan` CLI rendering, and avoid extra newlines in `client_req` rendering in some cases. (By Thomas Heymann.) - [pull #291, issue #303] Fix `LOG.child(...)` to *not* override the "hostname" field of the parent. A use case is when one manually sets "hostname" to something other than `os.hostname()`. (By github.com/Cactusbone.) - [issue #325] Allow one to set `level: 0` in `createLogger` to turn on logging for all levels. (Adapted from #336 by github.com/sometimesalready.) - Add guards (to `resolveLevel`) so that all "level" values are validated. Before this, a bogus level like "foo" or -12 or `['some', 'array']` would silently be accepted -- with undefined results. - Doc updates for #340 and #305. - Update `make test` to test against node 5, 4, 0.12 and 0.10. ## 1.5.1 - [issue #296] Fix `src: true`, which was broken in v1.5.0. ## 1.5.0 Note: *Bad release.* The addition of `'use strict';` broke Bunyan's `src: true` feature. Use 1.5.1 instead. - [pull #236, issue #231, issue #223] Fix strict mode in the browser. - [pull #282, issue #213] Fixes bunyan to work with webpack. By Denis Izmaylov. - [pull #294] Update to dtrace-provider 0.6 to fix with node 4.0 and io.js 3.0. - Dropped support for 0.8 (can't install deps easily anymore for running test suite). Bump to a recent iojs version for testing. ## 1.4.0 (Bumping minor ver b/c I'm wary of dtrace-provider changes. :) - [issue #258, pull #259] Update to dtrace-provider 0.5 to fix install and tests on recent io.js versions. - safe-json-stringify@1.0.3 changed output, breaking some tests. Fix those. ## 1.3.6 - [issue #244] Make `bunyan` defensive on `res.header=null`. ## 1.3.5 - [issue #233] Make `bunyan` defensive on res.header as a boolean. - [issue #242] Make `bunyan` defensive on err.stack not being a string. ## 1.3.4 - Allow `log.child(...)` to work even if the logger is a *sub-class* of Bunyan's Logger class. - [issue #219] Hide 'source-map-support' require from browserify. - [issue #218] Reset `haveNonRawStreams` on `.addStream`. ## 1.3.3 - [pull #127] Update to dtrace-provider 0.4.0, which gives io.js 1.x support for dtrace-y parts of Bunyan. ## 1.3.2 - [pull #182] Fallback to using the optional 'safe-json-stringify' module if `JSON.stringify` throws -- possibly with an enumerable property getter than throws. By Martin Gausby. ## 1.3.1 - Export `bunyan.RotatingFileStream` which is needed if one wants to customize it. E.g. see issue #194. - [pull #122] Source Map support for caller line position for [the "src" field](https://github.com/trentm/node-bunyan#src). This could be interesting for [CoffeeScript](http://coffeescript.org/documentation/docs/sourcemap.html) users of Bunyan. By Manuel Schneider. - [issue #164] Ensure a top-level `level` given in `bunyan.createLogger` is *used* for given `streams`. For example, ensure that the following results in the stream having a DEBUG level: var log = bunyan.createLogger({ name: 'foo', level: 'debug', streams: [ { path: '/var/tmp/foo.log' } ] }); This was broken in the 1.0.1 release. Between that release and 1.3.0 the "/var/tmp/foo.log" stream would be at the INFO level (Bunyan's default level). ## 1.3.0 - [issue #103] `bunyan -L` (or `bunyan --time local`) to show local time. Bunyan log records store `time` in UTC time. Sometimes it is convenient to display in local time. - [issue #205] Fix the "The Bunyan CLI crashed!" checking to properly warn of the common failure case when `-c CONDITION` is being used. ## 1.2.4 - [issue #210] Export `bunyan.nameFromLevel` and `bunyan.levelFromName`. It can be a pain for custom streams to have to reproduce that. - [issue #100] Gracefully handle the case of an unbound `Logger.{info,debug,...}` being used for logging, e.g.: myEmittingThing.on('data', log.info) Before this change, bunyan would throw. Now it emits a warning to stderr *once*, and then silently ignores those log attempts, e.g.: bunyan usage error: /Users/trentm/tm/node-bunyan/foo.js:12: attempt to log with an unbound log method: `this` is: { _events: { data: [Function] } } ## 1.2.3 - [issue #184] Fix log rotation for rotation periods > ~25 days. Before this change, a rotation period longer than this could hit [the maximum setTimeout delay in node.js](https://github.com/joyent/node/issues/8656). By Daniel Juhl. ## 1.2.2 - Drop the guard that a bunyan Logger level must be between TRACE (10) and FATAL (60), inclusive. This allows a trick of setting the level to `FATAL + 1` to turn logging off. While the standard named log levels are the golden path, then intention was not to get in the way of using other level numbers. ## 1.2.1 - [issue #178, #181] Get at least dtrace-provider 0.3.1 for optionalDependencies to get a fix for install with decoupled npm (e.g. with homebrew's node and npm). ## 1.2.0 - [issue #157] Restore dtrace-provider as a dependency (in "optionalDependencies"). Dtrace-provider version 0.3.0 add build sugar that should eliminate the problems from older versions: The build is not attempted on Linux and Windows. The build spew is *not* emitted by default (use `V=1 npm install` to see it); instead a short warning is emitted if the build fails. Also, importantly, the new dtrace-provider fixes working with node v0.11/0.12. ## 1.1.3 - [issue #165] Include extra `err` fields in `bunyan` CLI output. Before this change only the fields part of the typical node.js error stack (err.stack, err.message, err.name) would be emitted, even though the Bunyan *library* would typically include err.code and err.signal in the raw JSON log record. ## 1.1.2 - Fix a breakage in `log.info(err)` on a logger with no serializers. ## 1.1.1 Note: *Bad release.* It breaks `log.info(err)` on a logger with no serializers. Use version 1.1.2. - [pull #168] Fix handling of `log.info(err)` to use the `log` Logger's `err` serializer if it has one, instead of always using the core Bunyan err serializer. (By Mihai Tomescu.) ## 1.1.0 - [issue #162] Preliminary support for [browserify](http://browserify.org/). See [the section in the README](../README.md#browserify). ## 1.0.1 - [issues #105, #138, #151] Export `.addStream(...)` and `.addSerializers(...)` to be able to add them after Logger creation. Thanks @andreineculau! - [issue #159] Fix bad handling in construtor guard intending to allow creation without "new": `var log = Logger(...)`. Thanks @rmg! - [issue #156] Smaller install size via .npmignore file. - [issue #126, #161] Ignore SIGINT (Ctrl+C) when processing stdin. `...| bunyan` should expect the preceding process in the pipeline to handle SIGINT. While it is doing so, `bunyan` should continue to process any remaining output. Thanks @timborodin and @jnordberg! - [issue #160] Stop using ANSI 'grey' in `bunyan` CLI output, because of the problems that causes with Solarized Dark themes (see ). ## 1.0.0 - [issue #87] **Backward incompatible change to `-c CODE`** improving performance by over 10x (good!), with a backward incompatible change to semantics (unfortunate), and adding some sugar (good!). The `-c CODE` implementation was changed to use a JS function for processing rather than `vm.runInNewContext`. The latter was specatularly slow, so won't be missed. Unfortunately this does mean a few semantic differences in the `CODE`, the most noticeable of which is that **`this` is required to access the object fields:** # Bad. Works with bunyan 0.x but not 1.x. $ bunyan -c 'pid === 123' foo.log ... # Good. Works with all versions of bunyan $ bunyan -c 'this.pid === 123' foo.log ... The old behaviour of `-c` can be restored with the `BUNYAN_EXEC=vm` environment variable: $ BUNYAN_EXEC=vm bunyan -c 'pid === 123' foo.log ... Some sugar was also added: the TRACE, DEBUG, ... constants are defined, so one can: $ bunyan -c 'this.level >= ERROR && this.component === "http"' foo.log ... And example of the speed improvement on a 10 MiB log example: $ time BUNYAN_EXEC=vm bunyan -c 'this.level === ERROR' big.log | cat >slow real 0m6.349s user 0m6.292s sys 0m0.110s $ time bunyan -c 'this.level === ERROR' big.log | cat >fast real 0m0.333s user 0m0.303s sys 0m0.028s The change was courtesy Patrick Mooney (https://github.com/pfmooney). Thanks! - Add `bunyan -0 ...` shortcut for `bunyan -o bunyan ...`. - [issue #135] **Backward incompatible.** Drop dtrace-provider even from `optionalDependencies`. Dtrace-provider has proven a consistent barrier to installing bunyan, because it is a binary dep. Even as an *optional* dep it still caused confusion and install noise. Users of Bunyan on dtrace-y platforms (SmartOS, Mac, Illumos, Solaris) will need to manually `npm install dtrace-provider` themselves to get [Bunyan's dtrace support](https://github.com/trentm/node-bunyan#runtime-log-snooping-via-dtrace) to work. If not installed, bunyan should stub it out properly. ## 0.23.1 - [pull #125, pull #97, issue #73] Unref rotating-file timeout which was preventing processes from exiting (by https://github.com/chakrit and https://github.com/glenn-murray-bse). Note: this only fixes the issue for node 0.10 and above. ## 0.23.0 - [issue #139] Fix `bunyan` crash on a log record with `res.header` that is an object. A side effect of this improvement is that a record with `res.statusCode` but no header info will render a response block, for example: [2012-08-08T10:25:47.637Z] INFO: my-service/12859 on my-host: some message (...) ... -- HTTP/1.1 200 OK -- ... - [pull #42] Fix `bunyan` crash on a log record with `req.headers` that is a *string* (by https://github.com/aexmachina). - Drop node 0.6 support. I can't effectively `npm install` with a node 0.6 anymore. - [issue #85] Ensure logging a non-object/non-string doesn't throw (by https://github.com/mhart). This changes fixes: log.info() # TypeError: Object.keys called on non-object log.info() # "msg":"" (instead of wanted "msg":"[Function]") log.info() # "msg":"" (instead of wanted "msg":util.format()) ## 0.22.3 - Republish the same code to npm. ## 0.22.2 Note: Bad release. The published package in the npm registry got corrupted. Use 0.22.3 or later. - [issue #131] Allow `log.info()` and, most importantly, don't crash on that. - Update 'mv' optional dep to latest. ## 0.22.1 - [issue #111] Fix a crash when attempting to use `bunyan -p` on a platform without dtrace. - [issue #101] Fix a crash in `bunyan` rendering a record with unexpected "res.headers". ## 0.22.0 - [issue #104] `log.reopenFileStreams()` convenience method to be used with external log rotation. ## 0.21.4 - [issue #96] Fix `bunyan` to default to paging (with `less`) by default in node 0.10.0. The intention has always been to default to paging for node >=0.8. ## 0.21.3 - [issue #90] Fix `bunyan -p '*'` breakage in version 0.21.2. ## 0.21.2 **Note: Bad release. The switchrate change below broke `bunyan -p '*'` usage (see issue #90). Use 0.21.3 or later.** - [issue #88] Should be able to efficiently combine "-l" with "-p *". - Avoid DTrace buffer filling up, e.g. like this: $ bunyan -p 42241 > /tmp/all.log dtrace: error on enabled probe ID 3 (ID 75795: bunyan42241:mod-87ea640:log-trace:log-trace): out of scratch space in action #1 at DIF offset 12 dtrace: error on enabled probe ID 3 (ID 75795: bunyan42241:mod-87ea640:log-trace:log-trace): out of scratch space in action #1 at DIF offset 12 dtrace: 138 drops on CPU 4 ... From Bryan: "the DTrace buffer is filling up because the string size is so large... by increasing the switchrate, you're increasing the rate at which that buffer is emptied." ## 0.21.1 - [pull #83] Support rendering 'client_res' key in bunyan CLI (by github.com/mcavage). ## 0.21.0 - 'make check' clean, 4-space indenting. No functional change here, just lots of code change. - [issue #80, #82] Drop assert that broke using 'rotating-file' with a default `period` (by github.com/ricardograca). ## 0.20.0 - [Slight backward incompatibility] Fix serializer bug introduced in 0.18.3 (see below) to only apply serializers to log records when appropriate. This also makes a semantic change to custom serializers. Before this change a serializer function was called for a log record key when that value was truth-y. The semantic change is to call the serializer function as long as the value is not `undefined`. That means that a serializer function should handle falsey values such as `false` and `null`. - Update to latest 'mv' dep (required for rotating-file support) to support node v0.10.0. ## 0.19.0 **WARNING**: This release includes a bug introduced in bunyan 0.18.3 (see below). Please upgrade to bunyan 0.20.0. - [Slight backward incompatibility] Change the default error serialization (a.k.a. `bunyan.stdSerializers.err`) to *not* serialize all additional attributes of the given error object. This is an open door to unsafe logging and logging should always be safe. With this change, error serialization will log these attributes: message, name, stack, code, signal. The latter two are added because some core node APIs include those fields (e.g. `child_process.exec`). Concrete examples where this has hurt have been the "domain" change necessitating 0.18.3 and a case where [node-restify](https://github.com/mcavage/node-restify) uses an error object as the response object. When logging the `err` and `res` in the same log statement (common for restify audit logging), the `res.body` would be JSON stringified as '[Circular]' as it had already been emitted for the `err` key. This results in a WTF with the bunyan CLI because the `err.body` is not rendered. If you need the old behaviour back you will need to do this: var bunyan = require('bunyan'); var errSkips = { // Skip domain keys. `domain` especially can have huge objects that can // OOM your app when trying to JSON.stringify. domain: true, domain_emitter: true, domain_bound: true, domain_thrown: true }; bunyan.stdSerializers.err = function err(err) { if (!err || !err.stack) return err; var obj = { message: err.message, name: err.name, stack: getFullErrorStack(err) } Object.keys(err).forEach(function (k) { if (err[k] !== undefined && !errSkips[k]) { obj[k] = err[k]; } }); return obj; }; - "long" and "bunyan" output formats for the CLI. `bunyan -o long` is the default format, the same as before, just called "long" now instead of the cheesy "paul" name. The "bunyan" output format is the same as "json-0", just with a more convenient name. ## 0.18.3 **WARNING**: This release introduced a bug such that all serializers are applied to all log records even if the log record did not contain the key for that serializer. If a logger serializer function does not handle being given `undefined`, then you'll get warnings like this on stderr: bunyan: ERROR: This should never happen. This is a bug in or in this application. Exception from "foo" Logger serializer: Error: ... at Object.bunyan.createLogger.serializers.foo (.../myapp.js:20:15) at Logger._applySerializers (.../lib/bunyan.js:644:46) at Array.forEach (native) at Logger._applySerializers (.../lib/bunyan.js:640:33) ... and the following junk in written log records: "foo":"(Error in Bunyan log "foo" serializer broke field. See stderr for details.)" Please upgrade to bunyan 0.20.0. - Change the `bunyan.stdSerializers.err` serializer for errors to *exclude* [the "domain*" keys](http://nodejs.org/docs/latest/api/all.html#all_additions_to_error_objects). `err.domain` will include its assigned members which can arbitrarily large objects that are not intended for logging. - Make the "dtrace-provider" dependency optional. I hate to do this, but installing bunyan on Windows is made very difficult with this as a required dep. Even though "dtrace-provider" stubs out for non-dtrace-y platforms, without a compiler and Python around, node-gyp just falls over. ## 0.18.2 - [pull #67] Remove debugging prints in rotating-file support. (by github.com/chad3814). - Update to dtrace-provider@0.2.7. ## 0.18.1 - Get the `bunyan` CLI to **not** automatically page (i.e. pipe to `less`) if stdin isn't a TTY, or if following dtrace probe output (via `-p PID`), or if not given log file arguments. ## 0.18.0 - Automatic paging support in the `bunyan` CLI (similar to `git log` et al). IOW, `bunyan` will open your pager (by default `less`) and pipe rendered log output through it. A main benefit of this is getting colored logs with a pager without the pain. Before you had to explicit use `--color` to tell bunyan to color output when the output was not a TTY: bunyan foo.log --color | less -R # before bunyan foo.log # now Disable with the `--no-pager` option or the `BUNYAN_NO_PAGER=1` environment variable. Limitations: Only supported for node >=0.8. Windows is not supported (at least not yet). - Switch test suite to nodeunit (still using a node-tap'ish API via a helper). ## 0.17.0 - [issue #33] Log rotation support: var bunyan = require('bunyan'); var log = bunyan.createLogger({ name: 'myapp', streams: [{ type: 'rotating-file', path: '/var/log/myapp.log', count: 7, period: 'daily' }] }); - Tweak to CLI default pretty output: don't special case "latency" field. The special casing was perhaps nice, but less self-explanatory. Before: [2012-12-27T21:17:38.218Z] INFO: audit/45769 on myserver: handled: 200 (15ms, audit=true, bar=baz) GET /foo ... After: [2012-12-27T21:17:38.218Z] INFO: audit/45769 on myserver: handled: 200 (audit=true, bar=baz, latency=15) GET /foo ... - *Exit* CLI on EPIPE, otherwise we sit there useless processing a huge log file with, e.g. `bunyan huge.log | head`. ## 0.16.8 - Guards on `-c CONDITION` usage to attempt to be more user friendly. Bogus JS code will result in this: $ bunyan portal.log -c 'this.req.username==boo@foo' bunyan: error: illegal CONDITION code: SyntaxError: Unexpected token ILLEGAL CONDITION script: Object.prototype.TRACE = 10; Object.prototype.DEBUG = 20; Object.prototype.INFO = 30; Object.prototype.WARN = 40; Object.prototype.ERROR = 50; Object.prototype.FATAL = 60; this.req.username==boo@foo Error: SyntaxError: Unexpected token ILLEGAL at new Script (vm.js:32:12) at Function.Script.createScript (vm.js:48:10) at parseArgv (/Users/trentm/tm/node-bunyan-0.x/bin/bunyan:465:27) at main (/Users/trentm/tm/node-bunyan-0.x/bin/bunyan:1252:16) at Object. (/Users/trentm/tm/node-bunyan-0.x/bin/bunyan:1330:3) at Module._compile (module.js:449:26) at Object.Module._extensions..js (module.js:467:10) at Module.load (module.js:356:32) at Function.Module._load (module.js:312:12) at Module.runMain (module.js:492:10) And all CONDITION scripts will be run against a minimal valid Bunyan log record to ensure they properly guard against undefined values (at least as much as can reasonably be checked). For example: $ bunyan portal.log -c 'this.req.username=="bob"' bunyan: error: CONDITION code cannot safely filter a minimal Bunyan log record CONDITION script: Object.prototype.TRACE = 10; Object.prototype.DEBUG = 20; Object.prototype.INFO = 30; Object.prototype.WARN = 40; Object.prototype.ERROR = 50; Object.prototype.FATAL = 60; this.req.username=="bob" Minimal Bunyan log record: { "v": 0, "level": 30, "name": "name", "hostname": "hostname", "pid": 123, "time": 1355514346206, "msg": "msg" } Filter error: TypeError: Cannot read property 'username' of undefined at bunyan-condition-0:7:9 at Script.Object.keys.forEach.(anonymous function) [as runInNewContext] (vm.js:41:22) at parseArgv (/Users/trentm/tm/node-bunyan-0.x/bin/bunyan:477:18) at main (/Users/trentm/tm/node-bunyan-0.x/bin/bunyan:1252:16) at Object. (/Users/trentm/tm/node-bunyan-0.x/bin/bunyan:1330:3) at Module._compile (module.js:449:26) at Object.Module._extensions..js (module.js:467:10) at Module.load (module.js:356:32) at Function.Module._load (module.js:312:12) at Module.runMain (module.js:492:10) A proper way to do that condition would be: $ bunyan portal.log -c 'this.req && this.req.username=="bob"' ## 0.16.7 - [issue #59] Clear a possibly interrupted ANSI color code on signal termination. ## 0.16.6 - [issue #56] Support `bunyan -p NAME` to dtrace all PIDs matching 'NAME' in their command and args (using `ps -A -o pid,command | grep NAME` or, on SunOS `pgrep -lf NAME`). E.g.: bunyan -p myappname This is useful for usage of node's [cluster module](http://nodejs.org/docs/latest/api/all.html#all_cluster) where you'll have multiple worker processes. ## 0.16.5 - Allow `bunyan -p '*'` to capture bunyan dtrace probes from **all** processes. - issue #55: Add support for `BUNYAN_NO_COLOR` environment variable to turn off all output coloring. This is still overridden by the `--color` and `--no-color` options. ## 0.16.4 - issue #54: Ensure (again, see 0.16.2) that stderr from the dtrace child process (when using `bunyan -p PID`) gets through. There had been a race between exiting bunyan and the flushing of the dtrace process' stderr. ## 0.16.3 - Drop 'trentm-dtrace-provider' fork dep now that has been resolved. Back to dtrace-provider. ## 0.16.2 - Ensure that stderr from the dtrace child process (when using `bunyan -p PID`) gets through. The `pipe` usage wasn't working on SmartOS. This is important to show the user if they need to 'sudo'. ## 0.16.1 - Ensure that a possible dtrace child process (with using `bunyan -p PID`) is terminated on signal termination of the bunyan CLI (at least for SIGINT, SIGQUIT, SIGTERM, SIGHUP). ## 0.16.0 - Add `bunyan -p PID` support. This is a convenience wrapper that effectively calls: dtrace -x strsize=4k -qn 'bunyan$PID:::log-*{printf("%s", copyinstr(arg0))}' | bunyan ## 0.15.0 - issue #48: Dtrace support! The elevator pitch is you can watch all logging from all Bunyan-using process with something like this: dtrace -x strsize=4k -qn 'bunyan*:::log-*{printf("%d: %s: %s", pid, probefunc, copyinstr(arg0))}' And this can include log levels *below* what the service is actually configured to log. E.g. if the service is only logging at INFO level and you need to see DEBUG log messages, with this you can. Obviously this only works on dtrace-y platforms: Illumos derivatives of SunOS (e.g. SmartOS, OmniOS), Mac, FreeBSD. Or get the bunyan CLI to render logs nicely: dtrace -x strsize=4k -qn 'bunyan*:::log-*{printf("%s", copyinstr(arg0))}' | bunyan See for details. By Bryan Cantrill. ## 0.14.6 - Export `bunyan.safeCycles()`. This may be useful for custom `type == "raw"` streams that may do JSON stringification of log records themselves. Usage: var str = JSON.stringify(rec, bunyan.safeCycles()); - [issue #49] Allow a `log.child()` to specify the level of inherited streams. For example: # Before var childLog = log.child({...}); childLog.level('debug'); # After var childLog = log.child({..., level: 'debug'}); - Improve the Bunyan CLI crash message to make it easier to provide relevant details in a bug report. ## 0.14.5 - Fix a bug in the long-stack-trace error serialization added in 0.14.4. The symptom: bunyan@0.14.4: .../node_modules/bunyan/lib/bunyan.js:1002 var ret = ex.stack || ex.toString(); ^ TypeError: Cannot read property 'stack' of undefined at getFullErrorStack (.../node_modules/bunyan/lib/bunyan.js:1002:15) ... ## 0.14.4 - **Bad release. Use 0.14.5 instead.** - Improve error serialization to walk the chain of `.cause()` errors from the likes of `WError` or `VError` error classes from [verror](https://github.com/davepacheco/node-verror) and [restify v2.0](https://github.com/mcavage/node-restify). Example: [2012-10-11T00:30:21.871Z] ERROR: imgapi/99612 on 0525989e-2086-4270-b960-41dd661ebd7d: my-message ValidationFailedError: my-message; caused by TypeError: cause-error-message at Server.apiPing (/opt/smartdc/imgapi/lib/app.js:45:23) at next (/opt/smartdc/imgapi/node_modules/restify/lib/server.js:550:50) at Server.setupReq (/opt/smartdc/imgapi/lib/app.js:178:9) at next (/opt/smartdc/imgapi/node_modules/restify/lib/server.js:550:50) at Server.parseBody (/opt/smartdc/imgapi/node_modules/restify/lib/plugins/body_parser.js:15:33) at next (/opt/smartdc/imgapi/node_modules/restify/lib/server.js:550:50) at Server.parseQueryString (/opt/smartdc/imgapi/node_modules/restify/lib/plugins/query.js:40:25) at next (/opt/smartdc/imgapi/node_modules/restify/lib/server.js:550:50) at Server._run (/opt/smartdc/imgapi/node_modules/restify/lib/server.js:579:17) at Server._handle.log.trace.req (/opt/smartdc/imgapi/node_modules/restify/lib/server.js:480:38) Caused by: TypeError: cause-error-message at Server.apiPing (/opt/smartdc/imgapi/lib/app.js:40:25) at next (/opt/smartdc/imgapi/node_modules/restify/lib/server.js:550:50) at Server.setupReq (/opt/smartdc/imgapi/lib/app.js:178:9) at next (/opt/smartdc/imgapi/node_modules/restify/lib/server.js:550:50) at Server.parseBody (/opt/smartdc/imgapi/node_modules/restify/lib/plugins/body_parser.js:15:33) at next (/opt/smartdc/imgapi/node_modules/restify/lib/server.js:550:50) at Server.parseQueryString (/opt/smartdc/imgapi/node_modules/restify/lib/plugins/query.js:40:25) at next (/opt/smartdc/imgapi/node_modules/restify/lib/server.js:550:50) at Server._run (/opt/smartdc/imgapi/node_modules/restify/lib/server.js:579:17) at Server._handle.log.trace.req (/opt/smartdc/imgapi/node_modules/restify/lib/server.js:480:38) ## 0.14.2 - [issue #45] Fix bunyan CLI (default output mode) to not crash on a 'res' field that isn't a response object, but a string. ## 0.14.1 - [issue #44] Fix the default `bunyan` CLI output of a `res.body` that is an object instead of a string. See issue#38 for the same with `req.body`. ## 0.14.0 - [pull #41] Safe `JSON.stringify`ing of emitted log records to avoid blowing up on circular objects (by Isaac Schlueter). ## 0.13.5 - [issue #39] Fix a bug with `client_req` handling in the default output of the `bunyan` CLI. ## 0.13.4 - [issue #38] Fix the default `bunyan` CLI output of a `req.body` that is an object instead of a string. ## 0.13.3 - Export `bunyan.resolveLevel(NAME-OR-NUM)` to resolve a level name or number to its log level number value: > bunyan.resolveLevel('INFO') 30 > bunyan.resolveLevel('debug') 20 A side-effect of this change is that the uppercase level name is now allowed in the logger constructor. ## 0.13.2 - [issue #35] Ensure that an accidental `log.info(BUFFER)`, where BUFFER is a node.js Buffer object, doesn't blow up. ## 0.13.1 - [issue #34] Ensure `req.body`, `res.body` and other request/response fields are emitted by the `bunyan` CLI (mostly by Rob Gulewich). ## 0.13.0 - [issue #31] Re-instate defines for the (uppercase) log level names (TRACE, DEBUG, etc.) in `bunyan -c "..."` filtering condition code. E.g.: $ ... | bunyan -c 'level >= ERROR' ## 0.12.0 - [pull #32] `bunyan -o short` for more concise output (by Dave Pacheco). E.g.: 22:56:52.856Z INFO myservice: My message instead of: [2012-02-08T22:56:52.856Z] INFO: myservice/123 on example.com: My message ## 0.11.3 - Add '--strict' option to `bunyan` CLI to suppress all but legal Bunyan JSON log lines. By default non-JSON, and non-Bunyan lines are passed through. ## 0.11.2 - [issue #30] Robust handling of 'req' field without a 'headers' subfield in `bunyan` CLI. - [issue #31] Pull the TRACE, DEBUG, et al defines from `bunyan -c "..."` filtering code. This was added in v0.11.1, but has a significant adverse affect. ## 0.11.1 - **Bad release. The TRACE et al names are bleeding into the log records when using '-c'.** - Add defines for the (uppercase) log level names (TRACE, DEBUG, etc.) in `bunyan -c "..."` filtering condition code. E.g.: $ ... | bunyan -c 'level >= ERROR' ## 0.11.0 - [pull #29] Add -l/--level for level filtering, and -c/--condition for arbitrary conditional filtering (by github.com/isaacs): $ ... | bunyan -l error # filter out log records below error $ ... | bunyan -l 50 # numeric value works too $ ... | bunyan -c 'level===50' # equiv with -c filtering $ ... | bunyan -c 'pid===123' # filter on any field $ ... | bunyan -c 'pid===123' -c '_audit' # multiple filters ## 0.10.0 - [pull #24] Support for gzip'ed log files in the bunyan CLI (by github.com/mhart): $ bunyan foo.log.gz ... ## 0.9.0 - [pull #16] Bullet proof the `bunyan.stdSerializers` (by github.com/rlidwka). - [pull #15] The `bunyan` CLI will now chronologically merge multiple log streams when it is given multiple file arguments. (by github.com/davepacheco) $ bunyan foo.log bar.log ... merged log records ... - [pull #15] A new `bunyan.RingBuffer` stream class that is useful for keeping the last N log messages in memory. This can be a fast way to keep recent, and thus hopefully relevant, log messages. (by @dapsays, github.com/davepacheco) Potential uses: Live debugging if a running process could inspect those messages. One could dump recent log messages at a finer log level than is typically logged on [`uncaughtException`](http://nodejs.org/docs/latest/api/all.html#all_event_uncaughtexception). var ringbuffer = new bunyan.RingBuffer({ limit: 100 }); var log = new bunyan({ name: 'foo', streams: [{ type: 'raw', stream: ringbuffer, level: 'debug' }] }); log.info('hello world'); console.log(ringbuffer.records); - Add support for "raw" streams. This is a logging stream that is given raw log record objects instead of a JSON-stringified string. function Collector() { this.records = []; } Collector.prototype.write = function (rec) { this.records.push(rec); } var log = new Logger({ name: 'mylog', streams: [{ type: 'raw', stream: new Collector() }] }); See "examples/raw-stream.js". I expect raw streams to be useful for piping Bunyan logging to separate services (e.g. , ) or to separate in-process handling. - Add test/corpus/*.log files (accidentally excluded) so the test suite actually works(!). ## 0.8.0 - [pull #21] Bunyan loggers now re-emit `fs.createWriteStream` error events. By github.com/EvanOxfeld. See "examples/handle-fs-error.js" and "test/error-event.js" for details. var log = new Logger({name: 'mylog', streams: [{path: FILENAME}]}); log.on('error', function (err, stream) { // Handle error writing to or creating FILENAME. }); - jsstyle'ing (via `make check`) ## 0.7.0 - [issue #12] Add `bunyan.createLogger(OPTIONS)` form, as is more typical in node.js APIs. This'll eventually become the preferred form. ## 0.6.9 - Change `bunyan` CLI default output to color "src" info red. Before the "src" information was uncolored. The "src" info is the filename, line number and function name resulting from using `src: true` in `Logger` creation. I.e., the `(/Users/trentm/tm/node-bunyan/examples/hi.js:10)` in: [2012-04-10T22:28:58.237Z] INFO: myapp/39339 on banana.local (/Users/trentm/tm/node-bunyan/examples/hi.js:10): hi - Tweak `bunyan` CLI default output to still show an "err" field if it doesn't have a "stack" attribute. ## 0.6.8 - Fix bad bug in `log.child({...}, true);` where the added child fields **would be added to the parent's fields**. This bug only existed for the "fast child" path (that second `true` argument). A side-effect of fixing this is that the "fast child" path is only 5 times as fast as the regular `log.child`, instead of 10 times faster. ## 0.6.7 - [issue #6] Fix bleeding 'type' var to global namespace. (Thanks Mike!) ## 0.6.6 - Add support to the `bunyan` CLI taking log file path args, `bunyan foo.log`, in addition to the usual `cat foo.log | bunyan`. - Improve reliability of the default output formatting of the `bunyan` CLI. Before it could blow up processing log records missing some expected fields. ## 0.6.5 - ANSI coloring output from `bunyan` CLI tool (for the default output mode/style). Also add the '--color' option to force coloring if the output stream is not a TTY, e.g. `cat my.log | bunyan --color | less -R`. Use `--no-color` to disable coloring, e.g. if your terminal doesn't support ANSI codes. - Add 'level' field to log record before custom fields for that record. This just means that the raw record JSON will show the 'level' field earlier, which is a bit nicer for raw reading. ## 0.6.4 - [issue #5] Fix `log.info() -> boolean` to work properly. Previous all were returning false. Ditto all trace/debug/.../fatal methods. ## 0.6.3 - Allow an optional `msg` and arguments to the `log.info( err)` logging form. For example, before: log.debug(my_error_instance) // good log.debug(my_error_instance, "boom!") // wasn't allowed Now the latter is allowed if you want to expliciting set the log msg. Of course this applies to all the `log.{trace|debug|info...}()` methods. - `bunyan` cli output: clarify extra fields with quoting if empty or have spaces. E.g. 'cmd' and 'stderr' in the following: [2012-02-12T00:30:43.736Z] INFO: mo-docs/43194 on banana.local: buildDocs results (req_id=185edca2-2886-43dc-911c-fe41c09ec0f5, route=PutDocset, error=null, stderr="", cmd="make docs") ## 0.6.2 - Fix/guard against unintended inclusion of some files in npm published package due to ## 0.6.1 - Internal: starting jsstyle usage. - Internal: add .npmignore. Previous packages had reams of bunyan crud in them. ## 0.6.0 - Add 'pid' automatic log record field. ## 0.5.3 - Add 'client_req' (HTTP client request) standard formatting in `bunyan` CLI default output. - Improve `bunyan` CLI default output to include *all* log record keys. Unknown keys are either included in the first line parenthetical (if short) or in the indented subsequent block (if long or multiline). ## 0.5.2 - [issue #3] More type checking of `new Logger(...)` and `log.child(...)` options. - Start a test suite. ## 0.5.1 - [issue #2] Add guard on `JSON.stringify`ing of log records before emission. This will prevent `log.info` et al throwing on record fields that cannot be represented as JSON. An error will be printed on stderr and a clipped log record emitted with a 'bunyanMsg' key including error details. E.g.: bunyan: ERROR: could not stringify log record from /Users/trentm/tm/node-bunyan/examples/unstringifyable.js:12: TypeError: Converting circular structure to JSON { "name": "foo", "hostname": "banana.local", "bunyanMsg": "bunyan: ERROR: could not stringify log record from /Users/trentm/tm/node-bunyan/examples/unstringifyable.js:12: TypeError: Converting circular structure to JSON", ... Some timing shows this does effect log speed: $ node tools/timeguard.js # before Time try/catch-guard on JSON.stringify: - log.info: 0.07365ms per iteration $ node tools/timeguard.js # after Time try/catch-guard on JSON.stringify: - log.info: 0.07368ms per iteration ## 0.5.0 - Use 10/20/... instead of 1/2/... for level constant values. Ostensibly this allows for intermediary levels from the defined "trace/debug/..." set. However, that is discouraged. I'd need a strong user argument to add support for easily using alternative levels. Consider using a separate JSON field instead. - s/service/name/ for Logger name field. "service" is unnecessarily tied to usage for a service. No need to differ from log4j Logger "name". - Add `log.level(...)` and `log.levels(...)` API for changing logger stream levels. - Add `TRACE|DEBUG|INFO|WARN|ERROR|FATAL` level constants to exports. - Add `log.info(err)` special case for logging an `Error` instance. For example `log.info(new TypeError("boom")` will produce: ... "err": { "message": "boom", "name": "TypeError", "stack": "TypeError: boom\n at Object. ..." }, "msg": "boom", ... ## 0.4.0 - Add `new Logger({src: true})` config option to have a 'src' attribute be automatically added to log records with the log call source info. Example: "src": { "file": "/Users/trentm/tm/node-bunyan/examples/src.js", "line": 20, "func": "Wuzzle.woos" }, ## 0.3.0 - `log.child(options[, simple])` Added `simple` boolean arg. Set `true` to assert that options only add fields (no config changes). Results in a 10x speed increase in child creation. See "tools/timechild.js". On my Mac, "fast child" creation takes about 0.001ms. IOW, if your app is dishing 10,000 req/s, then creating a log child for each request will take about 1% of the request time. - `log.clone` -> `log.child` to better reflect the relationship: streams and serializers are inherited. Streams can't be removed as part of the child creation. The child doesn't own the parent's streams (so can't close them). - Clean up Logger creation. The goal here was to ensure `log.child` usage is fast. TODO: measure that. - Add `Logger.stdSerializers.err` serializer which is necessary to get good Error object logging with node 0.6 (where core Error object properties are non-enumerable). ## 0.2.0 - Spec'ing core/recommended log record fields. - Add `LOG_VERSION` to exports. - Improvements to request/response serializations. ## 0.1.0 First release. node-bunyan-2.0.5/CONTRIBUTING.md000066400000000000000000000213661377616564400162210ustar00rootroot00000000000000# Contributing to node-bunyan Thanks for using node-bunyan and for considering contributing to it! Or perhaps you are just here to get a sniff for what is going on with node-bunyan development. ## How you can help If you want to help me here, great! Thank you! Some ideas: - Do you have experience with and/or recommendations for a good automated testing service? Ideally I'd like support for Mac, Linux, SmartOS, and maybe Windows. Also, support for node.js versions 0.10 up to whatever the current latest is. Are those too tall an order? What's more, Bunyan is meant to work (at least partially) in the browser. Is there a good service for that? Please discuss on [issue #342](https://github.com/trentm/node-bunyan/issues/342). - Fielding issues labelled with "[Type-Question][Type-Question]", if you are familiar with Bunyan and know how to answer them, would be great. - If you want to dive into code, but aren't *that* familiar with node-bunyan, then [issues labelled with Experience-Easy][Experience-Easy] are a good place to start. - [Once I've made a once over triaging](https://github.com/trentm/node-bunyan/issues/335) and consolidating issues and PRs, volunteering for issues in a particular [component](#component) with which you have familiarity would be great. [Type-Question]: https://github.com/trentm/node-bunyan/issues?q=is%3Aopen+is%3Aissue+label%3AType-Question ## Trent's Biased Rules for Code In the hope that it makes it easier to get PRs into Bunyan, here is my biased list of what I typically want. Submitting a PR without all of these is *totally fine*! The only side-effect is that it may take longer for me to provide feedback on it and merge it. I'll politely request missing pieces. - Please follow existing code style. Contributed code must pass `make check`. (Note: I intended to [change to eslint soon](https://github.com/trentm/node-bunyan/issues/341), so currently `make check` might be a moving target.) - Any user visible change in behaviour should almost certainly include an update to the docs. Currently the "docs" is the README.md. - Adding a test case for code changes is **stronly recommended**, else I can't easily promise to not break your fix/feature later. If you don't grok the test suite, please ask. We can use it to form the basis for a "test/README.md". - Typically a code change should have an associated issue or PR. This allows addition of follow-up issues, discussion, test data, etc. to be associated with the commit. Just using GitHub pull requests makes this easy. - All but the most trivial code changes should have an addition to the [changelog](./CHANGES.md). The audience for the changelog is *Bunyan users*. However, because rebasing longer-lived PRs against master is a pain with a change to CHANGES.md, please **do not include a CHANGES.md change in your PR. Instead suggest a CHANGES.md addition in a comment on the PR.** - Good commit messages, please: - The first line should be a succinct summary of the issue or fix. A good candidate is to just cut 'n paste the issue title, if there is one. - If the commit is for a particular issue/PR (see previous rule), please list the issue number in the commit message. E.g. "Fixes #123" or "Related to #234". - The audience for commit messages is *Bunyan developers*. ## Pull Request Lifecycle (Language adapted from [terraform](https://github.com/hashicorp/terraform/blob/master/CONTRIBUTING.md).) - You are welcome to submit your pull request for commentary or review before it is fully completed. Please prefix the title of your pull request with "[WIP]" to indicate this. It's also a good idea to include specific questions or items you'd like feedback on. - Once you believe your pull request is ready to be merged, you can remove any "[WIP]" prefix from the title and a core team member will review. See Trent's Biased Rules above to help ensure that your contribution will be merged quickly. - Trent or, if things go well, a node-bunyan maintainer will look over your contribution and either provide comments letting you know if there is anything left to do. Please be patient. Unfortunately, I'm not able to carve out a *lot* of time for Bunyan development and maintenance. - Once all outstanding comments and checklist items have been addressed, your contribution will be merged. Merged PRs will be included in the next node-bunyan release. - In some cases, we might decide that a PR should be closed. We'll make sure to provide clear reasoning when this happens. ## Issue labels The point of issue labeling for node-bunyan is to help answer "what should be worked on now? what can be left for later?" I don't want issue labelling to become a burden for anyone, so (a) don't feel obliged to add them yourself and (b) I'm happy to reevaluate their usage. Bunyan shall have categories of [issue labels](https://github.com/trentm/node-bunyan/labels) named "$category-$value". An issue should have max *one* label from each set. Users of Google Code's dearly departed issue tracker may remember this kind of thing. This is a poorman's version of structured issue tracker metadata. I'm inclined to *not* do priorities right now. *Possibly* we'll use GitHub milestones to basically set targets for upcoming releases. But otherwise my sense is that for smaller OSS projects, assigning prios will get in the way. If people would think it helpful, I'd consider "Difficulty-" or "Experience-" categories (a la Rust's "E-" labels) to mark easier and intermediate tasks that someone interested but maybe not very familiar with Bunyan might want to tackle. For now, here are the various labels and their purpose: ### Meta - needstriage: Temporary label to help me do a single triage pass through all current open issues and PRs. See [#335](https://github.com/trentm/node-bunyan/issues/335) where I'm working through this. ### Type Color: green - Type-Unknown: If it is still unclear or undecided if an issue is an intended feature (perhaps arguing for better docs or examples to avoid confusion) or a bug, I will use this category. - Type-Question: Asking a question on Bunyan usage, about the project, etc. - Type-Bug: A bug in Bunyan's behaviour. - Type-Improvement: A new feature or other improvement. - Type-Doc: Issues with Bunyan's documentation. - Type-Task: A project task to be done. TODO: consider Type-Unknown for the "unclear if bug or feature" tickets. ### Component Color: blue - Component-Project: Project meta stuff like testing, linting, build, install, etc. - Component-CLI: The `bunyan` command-line tool. - Component-Lib: catch-all for other library stuff - Component-LibRotation: The bunyan library's log rotation support. - Component-LibBrowser: Bunyan's handling/support for running in the browser. - Component-LibFlush: A separate component for collecting the tickets related to closing/flushing bunyan streams on process shutdown. The point of components is to find like issues to help with reference, search and resolving them. If no component fits an issue/PR, then don't add a label. ### Resolution Color: red - Resolution-WontFix - Resolution-Duplicate - Resolution-Fixed: Also used to indicate "doc written", "question answered", "feature implemented". - Resolution-CannotRepro: After some reasonable attempt by maintainers to reproduce a bug report, I want it to be non-controversial to close it and mark it with this. If given more info by someone able to repro, we can happy re-open issues. ### Experience Color: yellow - Experience-Easy: Relatively little experience with node-bunyan should be required to complete this issue. - Experience-NeedsTest: Typically added to an issue or PR that needs a test case. Someone familiar enough with node-bunyan's test suite could tackle this. - Experience-Hard: At a guess, this is a thorny issue that requires known node-bunyan well, knowing node.js well, requires design review or all of these. One of the "Experience-\*" labels can optionally be put on an issue or PR to indicate what kind of experience a contributor would need with node-bunyan (and/or node.js) to complete it. For example, if you're looking for somewhere to start, check out the [Experience-Easy][Experience-Easy] tag. This category idea is borrowed from [rust's E-\* labels][rust-issue-triage]. [Experience-Easy]: https://github.com/trentm/node-bunyan/issues?q=is%3Aopen+is%3Aissue+label%3AExperience-Easy [rust-issue-triage]: https://github.com/rust-lang/rust/blob/master/CONTRIBUTING.md#issue-triage ## Acknowledgements Anything good about this document is thanks to inspiration from [rust](https://github.com/rust-lang/rust/blob/master/CONTRIBUTING.md) and, more recently [terraform](https://github.com/hashicorp/terraform/blob/master/CONTRIBUTING.md). Anything bad about it, is my fault. node-bunyan-2.0.5/LICENSE.txt000066400000000000000000000021211377616564400155770ustar00rootroot00000000000000# This is the MIT license Copyright 2016 Trent Mick Copyright 2016 Joyent Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. node-bunyan-2.0.5/Makefile000066400000000000000000000076151377616564400154310ustar00rootroot00000000000000 SHELL := bash #---- Tools TAP_EXEC := ./node_modules/.bin/tap SUDO := sudo ifeq ($(shell uname -s),SunOS) # On SunOS (e.g. SmartOS) we expect to run the test suite as the # root user -- necessary to run dtrace. Therefore `pfexec` isn't # necessary. SUDO := endif DTRACE_UP_IN_HERE= ifeq ($(shell uname -s),SunOS) DTRACE_UP_IN_HERE=1 endif ifeq ($(shell uname -s),Darwin) DTRACE_UP_IN_HERE=1 endif #---- Files JSSTYLE_FILES := $(shell find lib test tools examples -name "*.js") bin/bunyan #---- Targets all $(TAP_EXEC): npm install $(NPM_INSTALL_FLAGS) # Ensure all version-carrying files have the same version. .PHONY: versioncheck versioncheck: @echo version is: $(shell node -e 'console.log(require("./package.json").version)') [[ `node -e 'console.log(require("./package.json").version)'` == `grep '^## ' CHANGES.md | head -2 | tail -1 | awk '{print $$2}'` ]] @echo Version check ok. .PHONY: cutarelease cutarelease: check [[ -z `git status --short` ]] # If this fails, the working dir is dirty. @which json 2>/dev/null 1>/dev/null && \ ver=$(shell json -f package.json version) && \ name=$(shell json -f package.json name) && \ publishedVer=$(shell npm view -j $(shell json -f package.json name)@$(shell json -f package.json version) version 2>/dev/null) && \ if [[ -n "$$publishedVer" ]]; then \ echo "error: $$name@$$ver is already published to npm"; \ exit 1; \ fi && \ echo "** Are you sure you want to tag and publish $$name@$$ver to npm?" && \ echo "** Enter to continue, Ctrl+C to abort." && \ read ver=$(shell cat package.json | json version) && \ date=$(shell date -u "+%Y-%m-%d") && \ git tag -a "$$ver" -m "version $$ver ($$date) beta" && \ git push --tags origin && \ npm publish --tag beta .PHONY: docs docs: toc @[[ `which ronn` ]] || (echo "No 'ronn' on your PATH. Install with 'gem install ronn'" && exit 2) mkdir -p man/man1 ronn --style=toc --manual="bunyan manual" --date=$(shell git log -1 --pretty=format:%cd --date=short) --roff --html docs/bunyan.1.ronn python -c 'import sys; h = open("docs/bunyan.1.html").read(); h = h.replace(".mp dt.flush {float:left;width:8ex}", ""); open("docs/bunyan.1.html", "w").write(h)' python -c 'import sys; h = open("docs/bunyan.1.html").read(); h = h.replace("", """Fork me on GitHub"""); open("docs/bunyan.1.html", "w").write(h)' @echo "# test with 'man ./docs/bunyan.1' and 'open ./docs/bunyan.1.html'" # Re-generate the README.md table of contents. toc: ./node_modules/.bin/markdown-toc -i README.md .PHONY: publish publish: mkdir -p tmp [[ -d tmp/bunyan-gh-pages ]] || git clone git@github.com:trentm/node-bunyan.git tmp/bunyan-gh-pages cd tmp/bunyan-gh-pages && git checkout gh-pages && git pull --rebase origin gh-pages cp docs/index.html tmp/bunyan-gh-pages/index.html cp docs/bunyan.1.html tmp/bunyan-gh-pages/bunyan.1.html (cd tmp/bunyan-gh-pages \ && git commit -a -m "publish latest docs" \ && git push origin gh-pages || true) .PHONY: distclean distclean: rm -rf node_modules #---- test .PHONY: test test: $(TAP_EXEC) test -z "$(DTRACE_UP_IN_HERE)" || test -n "$(SKIP_DTRACE)" || \ (node -e 'require("dtrace-provider").createDTraceProvider("isthisthingon")' && \ echo "\nNote: Use 'SKIP_DTRACE=1 make test' to skip parts of the test suite that require root." && \ $(SUDO) $(TAP_EXEC) test/dtrace/*.test.js) $(TAP_EXEC) test/*.test.js #---- check .PHONY: check-jsstyle check-jsstyle: $(JSSTYLE_FILES) ./tools/jsstyle -o indent=4,doxygen,unparenthesized-return=0,blank-after-start-comment=0,leading-right-paren-ok=1 $(JSSTYLE_FILES) .PHONY: check check: check-jsstyle versioncheck @echo "Check ok." .PHONY: prepush prepush: check testall @echo "Okay to push." node-bunyan-2.0.5/README.md000066400000000000000000001330151377616564400152420ustar00rootroot00000000000000[![npm version](https://img.shields.io/npm/v/bunyan.svg?style=flat)](https://www.npmjs.com/package/bunyan) [![Build Status](https://travis-ci.org/trentm/node-bunyan.svg?branch=master)](https://travis-ci.org/trentm/node-bunyan) Bunyan is **a simple and fast JSON logging library** for node.js services: ```js var bunyan = require('bunyan'); var log = bunyan.createLogger({name: "myapp"}); log.info("hi"); ``` and **a `bunyan` CLI tool** for nicely viewing those logs: ![bunyan CLI screenshot](https://raw.github.com/trentm/node-bunyan/master/tools/screenshot1.png) Manifesto: Server logs should be structured. JSON's a good format. Let's do that. A log record is one line of `JSON.stringify`'d output. Let's also specify some common names for the requisite and common fields for a log record (see below). ## Table of Contents - [Current Status](#current-status) - [Installation](#installation) - [Features](#features) - [Introduction](#introduction) * [Constructor API](#constructor-api) * [Log Method API](#log-method-api) * [CLI Usage](#cli-usage) * [Streams Introduction](#streams-introduction) * [log.child](#logchild) * [Serializers](#serializers) + [Requirements for serializers functions](#requirements-for-serializers-functions) + [Standard Serializers](#standard-serializers) * [src](#src) - [Levels](#levels) * [Level suggestions](#level-suggestions) - [Log Record Fields](#log-record-fields) * [Core fields](#core-fields) * [Recommended/Best Practice Fields](#recommendedbest-practice-fields) * [Other fields to consider](#other-fields-to-consider) - [Streams](#streams) * [Adding a Stream](#adding-a-stream) * [stream errors](#stream-errors) * [stream type: `stream`](#stream-type-stream) * [stream type: `file`](#stream-type-file) * [stream type: `rotating-file`](#stream-type-rotating-file) * [stream type: `raw`](#stream-type-raw) * [`raw` + RingBuffer Stream](#raw--ringbuffer-stream) * [third-party streams](#third-party-streams) - [Runtime log snooping via DTrace](#runtime-log-snooping-via-dtrace) * [DTrace examples](#dtrace-examples) - [Runtime environments](#runtime-environments) * [Browserify](#browserify) * [Webpack](#webpack) - [Versioning](#versioning) - [License](#license) - [See Also](#see-also) # Current Status Stable. I do my best to follow semver: i.e. you should only need to worry about code breaking for a *major* version bump. Bunyan currently supports node 0.10 and greater. Follow @trentmick for updates to Bunyan. There is an email discussion list [bunyan-logging@googlegroups.com](mailto:bunyan-logging@googlegroups.com), also [as a forum in the browser](https://groups.google.com/forum/?fromgroups#!forum/bunyan-logging). Active branches: - "1.x" is for 1.x maintenance work, if any. 1.x releases are still "latest" in npm. - "master" is currently for coming Bunyan 2.x work. For now, 2.x releases are published to npm with the "beta" tag, meaning that `npm install bunyan` is still 1.x for now. To install 2.x use `npm install bunyan@2` or `npm install bunyan@beta`. # Installation ```sh npm install bunyan ``` **Tip**: The `bunyan` CLI tool is written to be compatible (within reason) with all versions of Bunyan logs. Therefore you might want to `npm install -g bunyan` to get the bunyan CLI on your PATH, then use local bunyan installs for node.js library usage of bunyan in your apps. **Tip**: Installing without optional dependencies can dramatically reduce bunyan's install size. **dtrace-provider** is used for dtrace features, **mv** is used for RotatingFileStream, and **moment** is used for local time. If you don't need these features, consider installing with the `--no-optional` flag. # Features - elegant [log method API](#log-method-api) - extensible [streams](#streams) system for controlling where log records go (to a stream, to a file, [log file rotation](#stream-type-rotating-file), etc.) - [`bunyan` CLI](#cli-usage) for pretty-printing and filtering of Bunyan logs - simple include of log call source location (file, line, function) with [`src: true`](#src) - lightweight specialization of Logger instances with [`log.child`](#logchild) - custom rendering of logged objects with ["serializers"](#serializers) - [Runtime log snooping via DTrace support](#runtime-log-snooping-via-dtrace) - Support for a few [runtime environments](#runtime-environments): Node.js, [Browserify](http://browserify.org/), [Webpack](https://webpack.github.io/), [NW.js](http://nwjs.io/). # Introduction Like most logging libraries you create a Logger instance and call methods named after the logging levels: ```js // hi.js var bunyan = require('bunyan'); var log = bunyan.createLogger({name: 'myapp'}); log.info('hi'); log.warn({lang: 'fr'}, 'au revoir'); ``` All loggers must provide a "name". This is somewhat akin to the log4j logger "name", but Bunyan doesn't do hierarchical logger names. **Bunyan log records are JSON.** A few fields are added automatically: "pid", "hostname", "time" and "v". ```sh $ node hi.js {"name":"myapp","hostname":"banana.local","pid":40161,"level":30,"msg":"hi","time":"2013-01-04T18:46:23.851Z","v":0} {"name":"myapp","hostname":"banana.local","pid":40161,"level":40,"lang":"fr","msg":"au revoir","time":"2013-01-04T18:46:23.853Z","v":0} ``` ## Constructor API ```js var bunyan = require('bunyan'); var log = bunyan.createLogger({ name: , // Required level: , // Optional, see "Levels" section stream: , // Optional, see "Streams" section streams: [, ...], // Optional, see "Streams" section serializers: , // Optional, see "Serializers" section src: , // Optional, see "src" section // Any other fields are added to all log records as is. foo: 'bar', ... }); ``` ## Log Method API The example above shows two different ways to call `log.info(...)`. The full API is: ```js log.info(); // Returns a boolean: is the "info" level enabled? // This is equivalent to `log.isInfoEnabled()` or // `log.isEnabledFor(INFO)` in log4j. log.info('hi'); // Log a simple string message (or number). log.info('hi %s', bob, anotherVar); // Uses `util.format` for msg formatting. log.info({foo: 'bar'}, 'hi'); // The first field can optionally be a "fields" object, which // is merged into the log record. log.info(err); // Special case to log an `Error` instance to the record. // This adds an "err" field with exception details // (including the stack) and sets "msg" to the exception // message. log.info(err, 'more on this: %s', more); // ... or you can specify the "msg". log.info({foo: 'bar', err: err}, 'some msg about this error'); // To pass in an Error *and* other fields, use the `err` // field name for the Error instance **and ensure your logger // has a `err` serializer.** One way to ensure the latter is: // var log = bunyan.createLogger({ // ..., // serializers: bunyan.stdSerializers // }); // See the "Serializers" section below for details. ``` Note that this implies **you cannot blindly pass any object as the first argument to log it** because that object might include fields that collide with Bunyan's [core record fields](#core-fields). In other words, `log.info(mywidget)` may not yield what you expect. Instead of a string representation of `mywidget` that other logging libraries may give you, Bunyan will try to JSON-ify your object. It is a Bunyan best practice to always give a field name to included objects, e.g.: ```js log.info({widget: mywidget}, ...) ``` This will dove-tail with [Bunyan serializer support](#serializers), discussed later. The same goes for all of Bunyan's log levels: `log.trace`, `log.debug`, `log.info`, `log.warn`, `log.error`, and `log.fatal`. See the [levels section](#levels) below for details and suggestions. ## CLI Usage Bunyan log output is a stream of JSON objects. This is great for processing, but not for reading directly. A **`bunyan` tool** is provided **for pretty-printing bunyan logs** and for **filtering** (e.g. `| bunyan -c 'this.foo == "bar"'`). Using our example above: ```sh $ node hi.js | ./node_modules/.bin/bunyan [2013-01-04T19:01:18.241Z] INFO: myapp/40208 on banana.local: hi [2013-01-04T19:01:18.242Z] WARN: myapp/40208 on banana.local: au revoir (lang=fr) ``` See the screenshot above for an example of the default coloring of rendered log output. That example also shows the nice formatting automatically done for some well-known log record fields (e.g. `req` is formatted like an HTTP request, `res` like an HTTP response, `err` like an error stack trace). One interesting feature is **filtering** of log content, which can be useful for digging through large log files or for analysis. We can filter only records above a certain level: ```sh $ node hi.js | bunyan -l warn [2013-01-04T19:08:37.182Z] WARN: myapp/40353 on banana.local: au revoir (lang=fr) ``` Or filter on the JSON fields in the records (e.g. only showing the French records in our contrived example): ```sh $ node hi.js | bunyan -c 'this.lang == "fr"' [2013-01-04T19:08:26.411Z] WARN: myapp/40342 on banana.local: au revoir (lang=fr) ``` See `bunyan --help` for other facilities. ## Streams Introduction By default, log output is to stdout and at the "info" level. Explicitly that looks like: ```js var log = bunyan.createLogger({ name: 'myapp', stream: process.stdout, level: 'info' }); ``` That is an abbreviated form for a single stream. **You can define multiple streams at different levels**. ```js var log = bunyan.createLogger({ name: 'myapp', streams: [ { level: 'info', stream: process.stdout // log INFO and above to stdout }, { level: 'error', path: '/var/tmp/myapp-error.log' // log ERROR and above to a file } ] }); ``` More on streams in the [Streams section](#streams) below. ## log.child Bunyan has a concept of a child logger to **specialize a logger for a sub-component of your application**, i.e. to create a new logger with additional bound fields that will be included in its log records. A child logger is created with `log.child(...)`. In the following example, logging on a "Wuzzle" instance's `this.log` will be exactly as on the parent logger with the addition of the `widget_type` field: ```js var bunyan = require('bunyan'); var log = bunyan.createLogger({name: 'myapp'}); function Wuzzle(options) { this.log = options.log.child({widget_type: 'wuzzle'}); this.log.info('creating a wuzzle') } Wuzzle.prototype.woos = function () { this.log.warn('This wuzzle is woosey.') } log.info('start'); var wuzzle = new Wuzzle({log: log}); wuzzle.woos(); log.info('done'); ``` Running that looks like (raw): ```sh $ node myapp.js {"name":"myapp","hostname":"myhost","pid":34572,"level":30,"msg":"start","time":"2013-01-04T07:47:25.814Z","v":0} {"name":"myapp","hostname":"myhost","pid":34572,"widget_type":"wuzzle","level":30,"msg":"creating a wuzzle","time":"2013-01-04T07:47:25.815Z","v":0} {"name":"myapp","hostname":"myhost","pid":34572,"widget_type":"wuzzle","level":40,"msg":"This wuzzle is woosey.","time":"2013-01-04T07:47:25.815Z","v":0} {"name":"myapp","hostname":"myhost","pid":34572,"level":30,"msg":"done","time":"2013-01-04T07:47:25.816Z","v":0} ``` And with the `bunyan` CLI (using the "short" output mode): ```sh $ node myapp.js | bunyan -o short 07:46:42.707Z INFO myapp: start 07:46:42.709Z INFO myapp: creating a wuzzle (widget_type=wuzzle) 07:46:42.709Z WARN myapp: This wuzzle is woosey. (widget_type=wuzzle) 07:46:42.709Z INFO myapp: done ``` A more practical example is in the [node-restify](https://github.com/mcavage/node-restify) web framework. Restify uses Bunyan for its logging. One feature of its integration, is that if `server.use(restify.requestLogger())` is used, each restify request handler includes a `req.log` logger that is: ```js log.child({req_id: }, true) ``` Apps using restify can then use `req.log` and have all such log records include the unique request id (as "req\_id"). Handy. ## Serializers Bunyan has a concept of **"serializer" functions to produce a JSON-able object from a JavaScript object**, so you can easily do the following: ```js log.info({req: }, 'something about handling this request'); ``` and have the `req` entry in the log record be just a reasonable subset of `` fields (or computed data about those fields). A logger instance can have a `serializers` mapping of log record field name ("req" in this example) to a serializer function. When creating the log record, Bunyan will call the serializer function for *top-level* fields of that name. An example: ```js function reqSerializer(req) { return { method: req.method, url: req.url, headers: req.headers }; } var log = bunyan.createLogger({ name: 'myapp', serializers: { req: reqSerializer } }); ``` Typically serializers are added to a logger at creation time via: ```js var log = bunyan.createLogger({ name: 'myapp', serializers: { foo: function fooSerializer(foo) { ... }, ... } }); // or var log = bunyan.createLogger({ name: 'myapp', serializers: bunyan.stdSerializers }); ``` Serializers can also be added after creation via `.addSerializers(...)`, e.g.: ```js var log = bunyan.createLogger({name: 'myapp'}); log.addSerializers({req: reqSerializer}); ``` ### Requirements for serializers functions A serializer function is passed unprotected objects that are passed to the `log.info`, `log.debug`, etc. call. This means a poorly written serializer function can cause side-effects. Logging shouldn't do that. Here are a few rules and best practices for serializer functions: - A serializer function *should never throw*. The bunyan library *does* protect somewhat from this: if the serializer throws an error, then bunyan will (a) write an ugly message on stderr (along with the traceback), and (b) the field in the log record will be replaced with a short error message. For example: ``` bunyan: ERROR: Exception thrown from the "foo" Bunyan serializer. This should never happen. This is a bug in that serializer function. TypeError: Cannot read property 'not' of undefined at Object.fooSerializer [as foo] (/Users/trentm/tm/node-bunyan/bar.js:8:26) at /Users/trentm/tm/node-bunyan/lib/bunyan.js:873:50 at Array.forEach (native) at Logger._applySerializers (/Users/trentm/tm/node-bunyan/lib/bunyan.js:865:35) at mkRecord (/Users/trentm/tm/node-bunyan/lib/bunyan.js:978:17) at Logger.info (/Users/trentm/tm/node-bunyan/lib/bunyan.js:1044:19) at Object. (/Users/trentm/tm/node-bunyan/bar.js:13:5) at Module._compile (module.js:409:26) at Object.Module._extensions..js (module.js:416:10) at Module.load (module.js:343:32) {"name":"bar","hostname":"danger0.local","pid":47411,"level":30,"foo":"(Error in Bunyan log \"foo\" serializer broke field. See stderr for details.)","msg":"one","time":"2017-03-08T02:53:51.173Z","v":0} ``` - A serializer function *should never mutate the given object*. Doing so will change the object in your application. - A serializer function *should be defensive*. In my experience, it is common to set a serializer in an app, say for field name "foo", and then accidentally have a log line that passes a "foo" that is undefined, or null, or of some unexpected type. A good start at defensiveness is to start with this: ```javascript function fooSerializer(foo) { // Guard against foo be null/undefined. Check that expected fields // are defined. if (!foo || !foo.bar) return foo; var obj = { // Create the object to be logged. bar: foo.bar } return obj; }; ``` ### Standard Serializers Bunyan includes a small set of "standard serializers", exported as `bunyan.stdSerializers`. Their use is completely optional. An example using all of them: ```js var log = bunyan.createLogger({ name: 'myapp', serializers: bunyan.stdSerializers }); ``` or particular ones: ```js var log = bunyan.createLogger({ name: 'myapp', serializers: {err: bunyan.stdSerializers.err} }); ``` Standard serializers are: | Field | Description | | ----- | ----------- | | err | Used for serializing JavaScript error objects, including traversing an error's cause chain for error objects with a `.cause()` -- e.g. as from [verror](https://github.com/joyent/node-verror). | | req | Common fields from a node.js HTTP request object. | | res | Common fields from a node.js HTTP response object. | Note that the `req` and `res` serializers intentionally do not include the request/response *body*, as that can be prohibitively large. If helpful, the [restify framework's audit logger plugin](https://github.com/restify/node-restify/blob/ac13902ad9716dcb20aaa62295403983075b1841/lib/plugins/audit.js#L38-L87) has its own req/res serializers that include more information (optionally including the body). ## src The **source file, line and function of the log call site** can be added to log records by using the `src: true` config option: ```js var log = bunyan.createLogger({src: true, ...}); ``` This adds the call source info with the 'src' field, like this: ```js { "name": "src-example", "hostname": "banana.local", "pid": 123, "component": "wuzzle", "level": 4, "msg": "This wuzzle is woosey.", "time": "2012-02-06T04:19:35.605Z", "src": { "file": "/Users/trentm/tm/node-bunyan/examples/src.js", "line": 20, "func": "Wuzzle.woos" }, "v": 0 } ``` **WARNING: Determining the call source info is slow. Never use this option in production.** # Levels The log levels in bunyan are as follows. The level descriptions are best practice *opinions* of the author. - "fatal" (60): The service/app is going to stop or become unusable now. An operator should definitely look into this soon. - "error" (50): Fatal for a particular request, but the service/app continues servicing other requests. An operator should look at this soon(ish). - "warn" (40): A note on something that should probably be looked at by an operator eventually. - "info" (30): Detail on regular operation. - "debug" (20): Anything else, i.e. too verbose to be included in "info" level. - "trace" (10): Logging from external libraries used by your app or *very* detailed application logging. Setting a logger instance (or one of its streams) to a particular level implies that all log records *at that level and above* are logged. E.g. a logger set to level "info" will log records at level info and above (warn, error, fatal). While using log level *names* is preferred, the actual level values are integers internally (10 for "trace", ..., 60 for "fatal"). Constants are defined for the levels: `bunyan.TRACE` ... `bunyan.FATAL`. The lowercase level names are aliases supported in the API, e.g. `log.level("info")`. There is one exception: DTrace integration uses the level names. The fired DTrace probes are named 'bunyan-$levelName'. Here is the API for querying and changing levels on an existing logger. Recall that a logger instance has an array of output "streams": ```js log.level() -> INFO // gets current level (lowest level of all streams) log.level(INFO) // set all streams to level INFO log.level("info") // set all streams to level INFO log.levels() -> [DEBUG, INFO] // get array of levels of all streams log.levels(0) -> DEBUG // get level of stream at index 0 log.levels("foo") // get level of stream with name "foo" log.levels(0, INFO) // set level of stream 0 to INFO log.levels(0, "info") // can use "info" et al aliases log.levels("foo", WARN) // set stream named "foo" to WARN ``` ## Level suggestions Trent's biased suggestions for server apps: Use "debug" sparingly. Information that will be useful to debug errors *post mortem* should usually be included in "info" messages if it's generally relevant or else with the corresponding "error" event. Don't rely on spewing mostly irrelevant debug messages all the time and sifting through them when an error occurs. Trent's biased suggestions for node.js libraries: IMHO, libraries should only ever log at `trace`-level. Fine control over log output should be up to the app using a library. Having a library that spews log output at higher levels gets in the way of a clear story in the *app* logs. # Log Record Fields This section will describe *rules* for the Bunyan log format: field names, field meanings, required fields, etc. However, a Bunyan library doesn't strictly enforce all these rules while records are being emitted. For example, Bunyan will add a `time` field with the correct format to your log records, but you can specify your own. It is the caller's responsibility to specify the appropriate format. The reason for the above leniency is because IMO logging a message should never break your app. This leads to this rule of logging: **a thrown exception from `log.info(...)` or equivalent (other than for calling with the incorrect signature) is always a bug in Bunyan.** A typical Bunyan log record looks like this: ```js {"name":"myserver","hostname":"banana.local","pid":123,"req":{"method":"GET","url":"/path?q=1#anchor","headers":{"x-hi":"Mom","connection":"close"}},"level":3,"msg":"start request","time":"2012-02-03T19:02:46.178Z","v":0} ``` Pretty-printed: ```js { "name": "myserver", "hostname": "banana.local", "pid": 123, "req": { "method": "GET", "url": "/path?q=1#anchor", "headers": { "x-hi": "Mom", "connection": "close" }, "remoteAddress": "120.0.0.1", "remotePort": 51244 }, "level": 3, "msg": "start request", "time": "2012-02-03T19:02:57.534Z", "v": 0 } ``` ## Core fields - `v`: Required. Integer. Added by Bunyan. Cannot be overridden. This is the Bunyan log format version (`require('bunyan').LOG_VERSION`). The log version is a single integer. `0` is until I release a version "1.0.0" of node-bunyan. Thereafter, starting with `1`, this will be incremented if there is any backward incompatible change to the log record format. Details will be in "CHANGES.md" (the change log). - `level`: Required. Integer. Added by Bunyan. Cannot be overridden. See the "Levels" section. - `name`: Required. String. Provided at Logger creation. You must specify a name for your logger when creating it. Typically this is the name of the service/app using Bunyan for logging. - `hostname`: Required. String. Provided or determined at Logger creation. You can specify your hostname at Logger creation or it will be retrieved via `os.hostname()`. - `pid`: Required. Integer. Filled in automatically at Logger creation. - `time`: Required. String. Added by Bunyan. Can be overridden. The date and time of the event in [ISO 8601 Extended Format](http://en.wikipedia.org/wiki/ISO_8601) format and in UTC, as from [`Date.toISOString()`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Date/toISOString). - `msg`: Required. String. Every `log.debug(...)` et al call must provide a log message. - `src`: Optional. Object giving log call source info. This is added automatically by Bunyan if the "src: true" config option is given to the Logger. Never use in production as this is really slow. Go ahead and add more fields, and nested ones are fine (and recommended) as well. This is why we're using JSON. Some suggestions and best practices follow (feedback from actual users welcome). ## Recommended/Best Practice Fields - `err`: Object. A caught JS exception. Log that thing with `log.info(err)` to get: ```js ... "err": { "message": "boom", "name": "TypeError", "stack": "TypeError: boom\n at Object. ..." }, "msg": "boom", ... ``` Or use the `bunyan.stdSerializers.err` serializer in your Logger and do this `log.error({err: err}, "oops")`. See "examples/err.js". - `req_id`: String. A request identifier. Including this field in all logging tied to handling a particular request to your server is strongly suggested. This allows post analysis of logs to easily collate all related logging for a request. This really shines when you have a SOA with multiple services and you carry a single request ID from the top API down through all APIs (as [node-restify](https://github.com/mcavage/node-restify) facilitates with its 'Request-Id' header). - `req`: An HTTP server request. Bunyan provides `bunyan.stdSerializers.req` to serialize a request with a suggested set of keys. Example: ```js { "method": "GET", "url": "/path?q=1#anchor", "headers": { "x-hi": "Mom", "connection": "close" }, "remoteAddress": "120.0.0.1", "remotePort": 51244 } ``` - `res`: An HTTP server response. Bunyan provides `bunyan.stdSerializers.res` to serialize a response with a suggested set of keys. Example: ```js { "statusCode": 200, "header": "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nConnection: keep-alive\r\nTransfer-Encoding: chunked\r\n\r\n" } ``` ## Other fields to consider - `req.username`: Authenticated user (or for a 401, the user attempting to auth). - Some mechanism to calculate response latency. "restify" users will have an "X-Response-Time" header. A `latency` custom field would be fine. - `req.body`: If you know that request bodies are small (common in APIs, for example), then logging the request body is good. # Streams A "stream" is Bunyan's name for where it outputs log messages (the equivalent to a log4j Appender). Ultimately Bunyan uses a [Writable Stream](https://nodejs.org/docs/latest/api/all.html#writable_Stream) interface, but there are some additional attributes used to create and manage the stream. A Bunyan Logger instance has one or more streams. In general streams are specified with the "streams" option: ```js var bunyan = require('bunyan'); var log = bunyan.createLogger({ name: "foo", streams: [ { stream: process.stderr, level: "debug" }, ... ] }); ``` For convenience, if there is only one stream, it can be specified with the "stream" and "level" options (internally converted to a `Logger.streams`). ```js var log = bunyan.createLogger({ name: "foo", stream: process.stderr, level: "debug" }); ``` Note that "file" streams do not support this shortcut (partly for historical reasons and partly to not make it difficult to add a literal "path" field on log records). If neither "streams" nor "stream" are specified, the default is a stream of type "stream" emitting to `process.stdout` at the "info" level. ## Adding a Stream After a bunyan instance has been initialized, you may add additional streams by calling the `addStream` function. ```js var bunyan = require('bunyan'); var log = bunyan.createLogger('myLogger'); log.addStream({ name: "myNewStream", stream: process.stderr, level: "debug" }); ``` ## stream errors A Bunyan logger instance can be made to re-emit "error" events from its streams. Bunyan does so by default for [`type === "file"` streams](#stream-type-file), so you can do this: ```js var log = bunyan.createLogger({name: 'mylog', streams: [{path: LOG_PATH}]}); log.on('error', function (err, stream) { // Handle stream write or create error here. }); ``` As of bunyan@1.7.0, the `reemitErrorEvents` field can be used when adding a stream to control whether "error" events are re-emitted on the Logger. For example: var EventEmitter = require('events').EventEmitter; var util = require('util'); function MyFlakyStream() {} util.inherits(MyFlakyStream, EventEmitter); MyFlakyStream.prototype.write = function (rec) { this.emit('error', new Error('boom')); } var log = bunyan.createLogger({ name: 'this-is-flaky', streams: [ { type: 'raw', stream: new MyFlakyStream(), reemitErrorEvents: true } ] }); log.info('hi there'); The behaviour is as follows: - `reemitErrorEvents` not specified: `file` streams will re-emit error events on the Logger instance. - `reemitErrorEvents: true`: error events will be re-emitted on the Logger for any stream with a `.on()` function -- which includes file streams, process.stdout/stderr, and any object that inherits from EventEmitter. - `reemitErrorEvents: false`: error events will not be re-emitted for any streams. Note: "error" events are **not** related to log records at the "error" level as produced by `log.error(...)`. See [the node.js docs on error events](https://nodejs.org/api/events.html#events_error_events) for details. ## stream type: `stream` A `type === 'stream'` is a plain ol' node.js [Writable Stream](http://nodejs.org/docs/latest/api/all.html#writable_Stream). A "stream" (the writable stream) field is required. E.g.: `process.stdout`, `process.stderr`. ```js var log = bunyan.createLogger({ name: 'foo', streams: [{ stream: process.stderr // `type: 'stream'` is implied }] }); ```
Field Required? Default Description
stream Yes - A "Writable Stream", e.g. a std handle or an open file write stream.
type No n/a `type == 'stream'` is implied if the `stream` field is given.
level No info The level to which logging to this stream is enabled. If not specified it defaults to "info". If specified this can be one of the level strings ("trace", "debug", ...) or constants (`bunyan.TRACE`, `bunyan.DEBUG`, ...). This serves as a severity threshold for that stream so logs of greater severity will also pass through (i.e. If level="warn", error and fatal will also pass through this stream).
name No - A name for this stream. This may be useful for usage of `log.level(NAME, LEVEL)`. See the [Levels section](#levels) for details. A stream "name" isn't used for anything else.
## stream type: `file` A `type === 'file'` stream requires a "path" field. Bunyan will open this file for appending. E.g.: ```js var log = bunyan.createLogger({ name: 'foo', streams: [{ path: '/var/log/foo.log', // `type: 'file'` is implied }] }); ```
Field Required? Default Description
path Yes - A file path to which to log.
type No n/a `type == 'file'` is implied if the `path` field is given.
level No info The level to which logging to this stream is enabled. If not specified it defaults to "info". If specified this can be one of the level strings ("trace", "debug", ...) or constants (`bunyan.TRACE`, `bunyan.DEBUG`, ...). This serves as a severity threshold for that stream so logs of greater severity will also pass through (i.e. If level="warn", error and fatal will also pass through this stream).
name No - A name for this stream. This may be useful for usage of `log.level(NAME, LEVEL)`. See the [Levels section](#levels) for details. A stream "name" isn't used for anything else.
## stream type: `rotating-file` **WARNING on node 0.8 usage:** Users of Bunyan's `rotating-file` should (a) be using at least bunyan 0.23.1 (with the fix for [this issue](https://github.com/trentm/node-bunyan/pull/97)), and (b) should use at least node 0.10 (node 0.8 does not support the `unref()` method on `setTimeout(...)` needed for the mentioned fix). The symptom is that process termination will hang for up to a full rotation period. **WARNING on [cluster](http://nodejs.org/docs/latest/api/all.html#all_cluster) usage:** Using Bunyan's `rotating-file` stream with node.js's "cluster" module can result in unexpected file rotation. You must not have multiple processes in the cluster logging to the same file path. In other words, you must have a separate log file path for the master and each worker in the cluster. Alternatively, consider using a system file rotation facility such as `logrotate` on Linux or `logadm` on SmartOS/Illumos. See [this comment on issue #117](https://github.com/trentm/node-bunyan/issues/117#issuecomment-44804938) for details. A `type === 'rotating-file'` is a file stream that handles file automatic rotation. ```js var log = bunyan.createLogger({ name: 'foo', streams: [{ type: 'rotating-file', path: '/var/log/foo.log', period: '1d', // daily rotation count: 3 // keep 3 back copies }] }); ``` This will rotate '/var/log/foo.log' every day (at midnight) to: ```sh /var/log/foo.log.0 # yesterday /var/log/foo.log.1 # 1 day ago /var/log/foo.log.2 # 2 days ago ``` *Currently*, there is no support for providing a template for the rotated files, or for rotating when the log reaches a threshold size.
Field Required? Default Description
type Yes - "rotating-file"
path Yes - A file path to which to log. Rotated files will be "$path.0", "$path.1", ...
period No 1d The period at which to rotate. This is a string of the format "$number$scope" where "$scope" is one of "ms" (milliseconds -- only useful for testing), "h" (hours), "d" (days), "w" (weeks), "m" (months), "y" (years). Or one of the following names can be used "hourly" (means 1h), "daily" (1d), "weekly" (1w), "monthly" (1m), "yearly" (1y). Rotation is done at the start of the scope: top of the hour (h), midnight (d), start of Sunday (w), start of the 1st of the month (m), start of Jan 1st (y).
count No 10 The number of rotated files to keep.
level No info The level at which logging to this stream is enabled. If not specified it defaults to "info". If specified this can be one of the level strings ("trace", "debug", ...) or constants (`bunyan.TRACE`, `bunyan.DEBUG`, ...).
name No - A name for this stream. This may be useful for usage of `log.level(NAME, LEVEL)`. See the [Levels section](#levels) for details. A stream "name" isn't used for anything else.
**Note on log rotation**: Often you may be using external log rotation utilities like `logrotate` on Linux or `logadm` on SmartOS/Illumos. In those cases, unless you are ensuring "copy and truncate" semantics (via `copytruncate` with logrotate or `-c` with logadm) then the fd for your 'file' stream will change. You can tell bunyan to reopen the file stream with code like this in your app: ```js var log = bunyan.createLogger(...); ... process.on('SIGUSR2', function () { log.reopenFileStreams(); }); ``` where you'd configure your log rotation to send SIGUSR2 (or some other signal) to your process. Any other mechanism to signal your app to run `log.reopenFileStreams()` would work as well. ## stream type: `raw` - `raw`: Similar to a "stream" writable stream, except that the write method is given raw log record *Object*s instead of a JSON-stringified string. This can be useful for hooking on further processing to all Bunyan logging: pushing to an external service, a RingBuffer (see below), etc. ## `raw` + RingBuffer Stream Bunyan comes with a special stream called a RingBuffer which keeps the last N records in memory and does *not* write the data anywhere else. One common strategy is to log 'info' and higher to a normal log file but log all records (including 'trace') to a ringbuffer that you can access via a debugger, or your own HTTP interface, or a post-mortem facility like MDB or node-panic. To use a RingBuffer: ```js /* Create a ring buffer that stores the last 100 records. */ var bunyan = require('bunyan'); var ringbuffer = new bunyan.RingBuffer({ limit: 100 }); var log = bunyan.createLogger({ name: 'foo', streams: [ { level: 'info', stream: process.stdout }, { level: 'trace', type: 'raw', // use 'raw' to get raw log record objects stream: ringbuffer } ] }); log.info('hello world'); console.log(ringbuffer.records); ``` This example emits: ```js [ { name: 'foo', hostname: '912d2b29', pid: 50346, level: 30, msg: 'hello world', time: '2012-06-19T21:34:19.906Z', v: 0 } ] ``` ## third-party streams See the [user-maintained list in the Bunyan wiki](https://github.com/trentm/node-bunyan/wiki/Awesome-Bunyan). # Runtime log snooping via DTrace On systems that support DTrace (e.g., illumos derivatives like SmartOS and OmniOS, FreeBSD, Mac), Bunyan will create a DTrace provider (`bunyan`) that makes available the following probes: ```sh log-trace log-debug log-info log-warn log-error log-fatal ``` Each of these probes has a single argument: the string that would be written to the log. Note that when a probe is enabled, it will fire whenever the corresponding function is called, even if the level of the log message is less than that of any stream. ## DTrace examples Trace all log messages coming from any Bunyan module on the system. (The `-x strsize=4k` is to raise dtrace's default 256 byte buffer size because log messages are longer than typical dtrace probes.) ```sh dtrace -x strsize=4k -qn 'bunyan*:::log-*{printf("%d: %s: %s", pid, probefunc, copyinstr(arg0))}' ``` Trace all log messages coming from the "wuzzle" component: ```sh dtrace -x strsize=4k -qn 'bunyan*:::log-*/strstr(this->str = copyinstr(arg0), "\"component\":\"wuzzle\"") != NULL/{printf("%s", this->str)}' ``` Aggregate debug messages from process 1234, by message: ```sh dtrace -x strsize=4k -n 'bunyan1234:::log-debug{@[copyinstr(arg0)] = count()}' ``` Have the bunyan CLI pretty-print the traced logs: ```sh dtrace -x strsize=4k -qn 'bunyan1234:::log-*{printf("%s", copyinstr(arg0))}' | bunyan ``` A convenience handle has been made for this: ```sh bunyan -p 1234 ``` On systems that support the [`jstack`](http://dtrace.org/blogs/dap/2012/04/25/profiling-node-js/) action via a node.js helper, get a stack backtrace for any debug message that includes the string "danger!": ```sh dtrace -x strsize=4k -qn 'log-debug/strstr(copyinstr(arg0), "danger!") != NULL/{printf("\n%s", copyinstr(arg0)); jstack()}' ``` Output of the above might be: ``` {"name":"foo","hostname":"763bf293-d65c-42d5-872b-4abe25d5c4c7.local","pid":12747,"level":20,"msg":"danger!","time":"2012-10-30T18:28:57.115Z","v":0} node`0x87e2010 DTraceProviderBindings.node`usdt_fire_probe+0x32 DTraceProviderBindings.node`_ZN4node11DTraceProbe5_fireEN2v85LocalINS1_5ValueEEE+0x32d DTraceProviderBindings.node`_ZN4node11DTraceProbe4FireERKN2v89ArgumentsE+0x77 << internal code >> (anon) as (anon) at /root/node-bunyan/lib/bunyan.js position 40484 << adaptor >> (anon) as doit at /root/my-prog.js position 360 (anon) as list.ontimeout at timers.js position 4960 << adaptor >> << internal >> << entry >> node`_ZN2v88internalL6InvokeEbNS0_6HandleINS0_10JSFunctionEEENS1_INS0_6ObjectEEEiPS5_Pb+0x101 node`_ZN2v88internal9Execution4CallENS0_6HandleINS0_6ObjectEEES4_iPS4_Pbb+0xcb node`_ZN2v88Function4CallENS_6HandleINS_6ObjectEEEiPNS1_INS_5ValueEEE+0xf0 node`_ZN4node12MakeCallbackEN2v86HandleINS0_6ObjectEEENS1_INS0_8FunctionEEEiPNS1_INS0_5ValueEEE+0x11f node`_ZN4node12MakeCallbackEN2v86HandleINS0_6ObjectEEENS1_INS0_6StringEEEiPNS1_INS0_5ValueEEE+0x66 node`_ZN4node9TimerWrap9OnTimeoutEP10uv_timer_si+0x63 node`uv__run_timers+0x66 node`uv__run+0x1b node`uv_run+0x17 node`_ZN4node5StartEiPPc+0x1d0 node`main+0x1b node`_start+0x83 node`0x87e2010 DTraceProviderBindings.node`usdt_fire_probe+0x32 DTraceProviderBindings.node`_ZN4node11DTraceProbe5_fireEN2v85LocalINS1_5ValueEEE+0x32d DTraceProviderBindings.node`_ZN4node11DTraceProbe4FireERKN2v89ArgumentsE+0x77 << internal code >> (anon) as (anon) at /root/node-bunyan/lib/bunyan.js position 40484 << adaptor >> (anon) as doit at /root/my-prog.js position 360 (anon) as list.ontimeout at timers.js position 4960 << adaptor >> << internal >> << entry >> node`_ZN2v88internalL6InvokeEbNS0_6HandleINS0_10JSFunctionEEENS1_INS0_6ObjectEEEiPS5_Pb+0x101 node`_ZN2v88internal9Execution4CallENS0_6HandleINS0_6ObjectEEES4_iPS4_Pbb+0xcb node`_ZN2v88Function4CallENS_6HandleINS_6ObjectEEEiPNS1_INS_5ValueEEE+0xf0 node`_ZN4node12MakeCallbackEN2v86HandleINS0_6ObjectEEENS1_INS0_8FunctionEEEiPNS1_INS0_5ValueEEE+0x11f node`_ZN4node12MakeCallbackEN2v86HandleINS0_6ObjectEEENS1_INS0_6StringEEEiPNS1_INS0_5ValueEEE+0x66 node`_ZN4node9TimerWrap9OnTimeoutEP10uv_timer_si+0x63 node`uv__run_timers+0x66 node`uv__run+0x1b node`uv_run+0x17 node`_ZN4node5StartEiPPc+0x1d0 node`main+0x1b node`_start+0x83 ``` # Runtime environments Node-bunyan supports running in a few runtime environments: - [Node.js](https://nodejs.org/) - [Browserify](http://browserify.org/): See the [Browserify section](#browserify) below. - [Webpack](https://webpack.github.io/): See the [Webpack section](#webpack) below. - [NW.js](http://nwjs.io/) Support for other runtime environments is welcome. If you have suggestions, fixes, or mentions that node-bunyan already works in some other JavaScript runtime, please open an [issue](https://github.com/trentm/node-bunyan/issues/new) or a pull request. The primary target is Node.js. It is the only environment in which I regularly test. If you have suggestions for how to automate testing for other environments, I'd appreciate feedback on [this automated testing issue](https://github.com/trentm/node-bunyan/issues/342). ## Browserify As the [Browserify](http://browserify.org/) site says it "lets you `require('modules')` in the browser by bundling up all of your dependencies." It is a build tool to run on your node.js script to bundle up your script and all its node.js dependencies into a single file that is runnable in the browser via: ```html ``` As of version 1.1.0, node-bunyan supports being run via Browserify. The default [stream](#streams) when running in the browser is one that emits raw log records to `console.log/info/warn/error`. Here is a quick example showing you how you can get this working for your script. 1. Get browserify and bunyan installed in your module: ```sh $ npm install browserify bunyan ``` 2. An example script using Bunyan, "play.js": ```js var bunyan = require('bunyan'); var log = bunyan.createLogger({name: 'play', level: 'debug'}); log.trace('this one does not emit'); log.debug('hi on debug'); // console.log log.info('hi on info'); // console.info log.warn('hi on warn'); // console.warn log.error('hi on error'); // console.error ``` 3. Build this into a bundle to run in the browser, "play.browser.js": ```sh $ ./node_modules/.bin/browserify play.js -o play.browser.js ``` 4. Put that into an HTML file, "play.html": ```html
hi
``` 5. Open that in your browser and open your browser console: ```sh $ open play.html ``` Here is what it looks like in Firefox's console: ![Bunyan + Browserify in the Firefox console](./docs/img/bunyan.browserify.png) For some, the raw log records might not be desired. To have a rendered log line you'll want to add your own stream, starting with something like this: ```js var bunyan = require('./lib/bunyan'); function MyRawStream() {} MyRawStream.prototype.write = function (rec) { console.log('[%s] %s: %s', rec.time.toISOString(), bunyan.nameFromLevel[rec.level], rec.msg); } var log = bunyan.createLogger({ name: 'play', streams: [ { level: 'info', stream: new MyRawStream(), type: 'raw' } ] }); log.info('hi on info'); ``` ## webpack To include bunyan in your webpack bundle you need to tell webpack to ignore the optional dependencies that are unavailable in browser environments. Mark the following dependencies as [externals](https://webpack.js.org/configuration/externals/) in your webpack configuration file to exclude them from the bundle: ``` module: { externals: ['dtrace-provider', 'fs', 'mv', 'os', 'source-map-support'] } ``` # Versioning All versions are `..` which will be incremented for breaking backward compat and major reworks, new features without breaking change, and bug fixes, respectively. tl;dr: [Semantic versioning](http://semver.org/). # License [MIT](./LICENSE.txt). # See Also See the [user-maintained list of Bunyan-related software in the Bunyan wiki](https://github.com/trentm/node-bunyan/wiki/Awesome-Bunyan). node-bunyan-2.0.5/TODO.md000066400000000000000000000130701377616564400150500ustar00rootroot00000000000000# v2 - `createLogger(, )` changes (#460) - see section below - the dtrace-provider thing (#487) TODO: answer Cody email - use package.json version for VERSION - use deps - dashdash - assert-plus? - verror? - break out to multiple files - want to work through PRs before that, so don't just break them all - TODO: a quick pass through tickets and pulls for other things to include - get ticket refs for the above, if any - formatters: read up again on `glp master..1.x` - support for customer formatters - for the CLI as well? How? ~/.bunyanrc? - if doing ~/.bunyanrc, then consider color schemes # changes to ctor and log.child to separate fields from config Current: createLogger() log.child(, ) Could be: createLogger(, ) log.child(, ) # Still support: log.child(, ) Pros: Compat issues are minimal: a change is only required if there is a collision with used field and a new config var name. Cons: A *slight* con is that my guess is the common usage of child is `log.child()`, so the more future-proof common usage becomes: log.child(null, ) That's not too bad. It is clearer at least than: log.child(, true) TODO: - is there a ticket for this work already? #460 - make the change - do a migration guide? i.e. provide the grep commands to find all possible calls to inspect. E.g. if don't have `rg logUndefined` in your code, then you are fine. And one time future-proofing via changing to fields in the *second* arg. - list of issues/pulls that wanted to add new config fields # higher prio - published organized advice for https://github.com/trentm/node-bunyan/issues/37 so can close that out. Perhaps a wiki page with examples and strategies. - man page for the bunyan CLI (refer to it in the readme) - perhaps wait for a bunyan new version with deps, and use dashdash with a (vapour) man page generator # docs - document log.addStream() and log.addSerializers() # someday/maybe - 2.0 (?) with `v: 1` in log records. Fwd/bwd compat in `bunyan` CLI - `tail -f`-like support - full-on docs - better examples/ - better coloring - look at pino (bunyan style, perf benefits), also logpp (https://github.com/mrkmarron/logpp) - would be exciting to have bunyan support in http://lnav.org/ if that made sense - "template" support for 'rotating-file' stream to get dated rolled files - "all" or "off" levels? log4j? logging.py? logging.py has NOTSET === 0. I think that is only needed/used for multi-level hierarchical effective level. - buffered writes to increase speed: - I'd start with a tools/timeoutput.js for some numbers to compare before/after. Sustained high output to a file. - perhaps this would be a "buffered: true" option on the stream object - then wrap the "stream" with a local class that handles the buffering - to finish this, need the 'log.close' and `process.on('exit', ...)` work that Trent has started. - "canWrite" handling for full streams. Need to buffer a la log4js - test file log with logadm rotation: does it handle that? - test suite: - test for a cloned logger double-`stream.end()` causing problems. Perhaps the "closeOnExit" for existing streams should be false for clones. - test that a `log.clone(...)` adding a new field matching a serializer works *and* that an existing field in the parent is not *re-serialized*. - split out `bunyan` cli to a "bunyan" or "bunyan-reader" or "node-bunyan-reader" as the basis for tools to consume bunyan logs. It can grow indep of node-bunyan for generating the logs. It would take a Bunyan log record object and be expected to emit it. node-bunyan-reader .createReadStream(path, [options]) ? - coloring bug: in less the indented extra info lines only have the first line colored. Do we need the ANSI char on *each* line? That'll be slower. - document "well-known" keys from bunyan CLI p.o.v.. Add "client_req". - More `bunyan` output formats and filtering features. - Think about a bunyan dashboard that supports organizing and viewing logs from multiple hosts and services. - doc the restify RequestCaptureStream usage of RingBuffer. Great example. - A vim plugin (a la http://vim.cybermirror.org/runtime/autoload/zip.vim ?) to allow browsing (read-only) a bunyan log in rendered form. - Some speed comparisons with others to get a feel for Bunyan's speed. - what about promoting 'latency' field and making that easier? - `log.close` to close streams and shutdown and `this.closed` process.on('exit', log.close) -> 'end' for the name - bunyan cli: more layouts (http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/EnhancedPatternLayout.html) Custom log formats (in config file? in '-f' arg) using printf or hogan.js or whatever. Dap wants field width control for lining up. Hogan.js is probably overkill for this. - loggly example using raw streams, hook.io?, whatever. - serializer support: - restify-server.js example -> restifyReq ? or have `req` detect that. That is nicer for the "use all standard ones". *Does* restify req have anything special? - differential HTTP *client* req/res with *server* req/res. - statsd stream? http://codeascraft.etsy.com/2011/02/15/measure-anything-measure-everything/ Think about it. - web ui. Ideas: http://googlecloudplatform.blogspot.ca/2014/04/a-new-logs-viewer-for-google-cloud.html node-bunyan-2.0.5/bin/000077500000000000000000000000001377616564400145305ustar00rootroot00000000000000node-bunyan-2.0.5/bin/bunyan000077500000000000000000001533471377616564400157670ustar00rootroot00000000000000#!/usr/bin/env node /** * Copyright 2020 Trent Mick * Copyright 2020 Joyent Inc. * * bunyan -- filter and pretty-print Bunyan log files (line-delimited JSON) * * See . * * -*- mode: js -*- * vim: expandtab:ts=4:sw=4 */ var p = console.log; var util = require('util'); var pathlib = require('path'); var vm = require('vm'); var http = require('http'); var fs = require('fs'); var warn = console.warn; var child_process = require('child_process'), spawn = child_process.spawn, exec = child_process.exec, execFile = child_process.execFile; var assert = require('assert'); var exeunt = require('exeunt'); try { var moment = require('moment'); } catch (e) { moment = null; } //---- globals and constants var nodeVer = process.versions.node.split('.').map(Number); var nodeSpawnSupportsStdio = (nodeVer[0] > 0 || nodeVer[1] >= 8); // Internal debug logging via `console.warn`. var _selfTrace = function selfTraceNoop() {}; if (process.env.BUNYAN_SELF_TRACE === '1') { _selfTrace = function selfTrace() { process.stderr.write('[bunyan self-trace] '); console.warn.apply(null, arguments); } } // Output modes. var OM_LONG = 1; var OM_JSON = 2; var OM_INSPECT = 3; var OM_SIMPLE = 4; var OM_SHORT = 5; var OM_BUNYAN = 6; var OM_FROM_NAME = { 'long': OM_LONG, 'paul': OM_LONG, /* backward compat */ 'json': OM_JSON, 'inspect': OM_INSPECT, 'simple': OM_SIMPLE, 'short': OM_SHORT, 'bunyan': OM_BUNYAN }; // Levels var TRACE = 10; var DEBUG = 20; var INFO = 30; var WARN = 40; var ERROR = 50; var FATAL = 60; var levelFromName = { 'trace': TRACE, 'debug': DEBUG, 'info': INFO, 'warn': WARN, 'error': ERROR, 'fatal': FATAL }; var nameFromLevel = {}; var upperNameFromLevel = {}; var upperPaddedNameFromLevel = {}; Object.keys(levelFromName).forEach(function (name) { var lvl = levelFromName[name]; nameFromLevel[lvl] = name; upperNameFromLevel[lvl] = name.toUpperCase(); upperPaddedNameFromLevel[lvl] = ( name.length === 4 ? ' ' : '') + name.toUpperCase(); }); // Display time formats. var TIME_UTC = 1; // the default, bunyan's native format var TIME_LOCAL = 2; // Timezone formats: output format -> momentjs format string var TIMEZONE_UTC_FORMATS = { long: '[[]YYYY-MM-DD[T]HH:mm:ss.SSS[Z][]]', short: 'HH:mm:ss.SSS[Z]' }; var TIMEZONE_LOCAL_FORMATS = { long: '[[]YYYY-MM-DD[T]HH:mm:ss.SSSZ[]]', short: 'HH:mm:ss.SSS' }; // Boolean set to true when we are in the process of exiting. We don't always // hard-exit (e.g. when staying alive while the pager completes). var exiting = false; // The current raw input line being processed. Used for `uncaughtException`. var currLine = null; // Child dtrace process, if any. Used for signal-handling. var child = null; // Whether ANSI codes are being used. Used for signal-handling. var usingAnsiCodes = false; // Used to tell the 'uncaughtException' handler that '-c CODE' is being used. var gUsingConditionOpts = false; // Pager child process, and output stream to which to write. var pager = null; var stdout = process.stdout; //---- support functions var _version = null; function getVersion() { if (_version === null) { _version = require('../package.json').version; } return _version; } var format = util.format; if (!format) { /* BEGIN JSSTYLED */ // If not node 0.6, then use its `util.format`: // : var inspect = util.inspect; var formatRegExp = /%[sdj%]/g; format = function format(f) { if (typeof f !== 'string') { var objects = []; for (var i = 0; i < arguments.length; i++) { objects.push(inspect(arguments[i])); } return objects.join(' '); } var i = 1; var args = arguments; var len = args.length; var str = String(f).replace(formatRegExp, function (x) { if (i >= len) return x; switch (x) { case '%s': return String(args[i++]); case '%d': return Number(args[i++]); case '%j': return JSON.stringify(args[i++]); case '%%': return '%'; default: return x; } }); for (var x = args[i]; i < len; x = args[++i]) { if (x === null || typeof x !== 'object') { str += ' ' + x; } else { str += ' ' + inspect(x); } } return str; }; /* END JSSTYLED */ } function indent(s) { return ' ' + s.split(/\r?\n/).join('\n '); } function objCopy(obj) { if (obj === null) { return null; } else if (Array.isArray(obj)) { return obj.slice(); } else { var copy = {}; Object.keys(obj).forEach(function (k) { copy[k] = obj[k]; }); return copy; } } function printHelp() { /* BEGIN JSSTYLED */ p('Usage:'); p(' bunyan [OPTIONS] [FILE ...]'); p(' ... | bunyan [OPTIONS]'); p(' bunyan [OPTIONS] -p PID'); p(''); p('Filter and pretty-print Bunyan log file content.'); p(''); p('General options:'); p(' -h, --help print this help info and exit'); p(' --version print version of this command and exit'); p(''); p('Runtime log snooping (via DTrace, only on supported platforms):'); p(' -p PID Process bunyan:log-* probes from the process'); p(' with the given PID. Can be used multiple times,'); p(' or specify all processes with "*", or a set of'); p(' processes whose command & args match a pattern'); p(' with "-p NAME".'); p(''); p('Filtering options:'); p(' -l, --level LEVEL'); p(' Only show messages at or above the specified level.'); p(' You can specify level *names* or the internal numeric'); p(' values.'); p(' -c, --condition CONDITION'); p(' Run each log message through the condition and'); p(' only show those that return truish. E.g.:'); p(' -c \'this.pid == 123\''); p(' -c \'this.level == DEBUG\''); p(' -c \'this.msg.indexOf("boom") != -1\''); p(' "CONDITION" must be legal JS code. `this` holds'); p(' the log record. The TRACE, DEBUG, ... FATAL values'); p(' are defined to help with comparing `this.level`.'); p(' --strict Suppress all but legal Bunyan JSON log lines. By default'); p(' non-JSON, and non-Bunyan lines are passed through.'); p(''); p('Output options:'); p(' --pager Pipe output into `less` (or $PAGER if set), if'); p(' stdout is a TTY. This overrides $BUNYAN_NO_PAGER.'); p(' Note: Paging is only supported on node >=0.8.'); p(' --no-pager Do not pipe output into a pager.'); p(' --color Colorize output. Defaults to try if output'); p(' stream is a TTY.'); p(' --no-color Force no coloring (e.g. terminal doesn\'t support it)'); p(' -o, --output MODE'); p(' Specify an output mode/format. One of'); p(' long: (the default) pretty'); p(' json: JSON output, 2-space indent'); p(' json-N: JSON output, N-space indent, e.g. "json-4"'); p(' bunyan: 0 indented JSON, bunyan\'s native format'); p(' inspect: node.js `util.inspect` output'); p(' short: like "long", but more concise'); p(' simple: level, followed by "-" and then the message'); p(' -j shortcut for `-o json`'); p(' -0 shortcut for `-o bunyan`'); p(' -L, --time local'); p(' Display time field in local time, rather than UTC.'); p(''); p('Environment Variables:'); p(' BUNYAN_NO_COLOR Set to a non-empty value to force no output '); p(' coloring. See "--no-color".'); p(' BUNYAN_NO_PAGER Disable piping output to a pager. '); p(' See "--no-pager".'); p(''); p('See for more complete docs.'); p('Please report bugs to .'); /* END JSSTYLED */ } /* * If the user specifies multiple input sources, we want to print out records * from all sources in a single, chronologically ordered stream. To do this * efficiently, we first assume that all records within each source are ordered * already, so we need only keep track of the next record in each source and * the time of the last record emitted. To avoid excess memory usage, we * pause() streams that are ahead of others. * * 'streams' is an object indexed by source name (file name) which specifies: * * stream Actual stream object, so that we can pause and resume it. * * records Array of log records we've read, but not yet emitted. Each * record includes 'line' (the raw line), 'rec' (the JSON * record), and 'time' (the parsed time value). * * done Whether the stream has any more records to emit. */ var streams = {}; function gotRecord(file, line, rec, opts, stylize) { var time = new Date(rec.time); streams[file]['records'].push({ line: line, rec: rec, time: time }); emitNextRecord(opts, stylize); } function filterRecord(rec, opts) { if (opts.level && rec.level < opts.level) { return false; } if (opts.condFuncs) { var recCopy = objCopy(rec); for (var i = 0; i < opts.condFuncs.length; i++) { var pass = opts.condFuncs[i].call(recCopy); if (!pass) return false; } } else if (opts.condVm) { for (var i = 0; i < opts.condVm.length; i++) { var pass = opts.condVm[i].runInNewContext(rec); if (!pass) return false; } } return true; } function emitNextRecord(opts, stylize) { var ofile, ready, minfile, rec; for (;;) { /* * Take a first pass through the input streams to see if we have a * record from all of them. If not, we'll pause any streams for * which we do already have a record (to avoid consuming excess * memory) and then wait until we have records from the others * before emitting the next record. * * As part of the same pass, we look for the earliest record * we have not yet emitted. */ minfile = undefined; ready = true; for (ofile in streams) { if (streams[ofile].stream === null || (!streams[ofile].done && streams[ofile].records.length === 0)) { ready = false; break; } if (streams[ofile].records.length > 0 && (minfile === undefined || streams[minfile].records[0].time > streams[ofile].records[0].time)) { minfile = ofile; } } if (!ready || minfile === undefined) { for (ofile in streams) { if (!streams[ofile].stream || streams[ofile].done) continue; if (streams[ofile].records.length > 0) { if (!streams[ofile].paused) { streams[ofile].paused = true; streams[ofile].stream.pause(); } } else if (streams[ofile].paused) { streams[ofile].paused = false; streams[ofile].stream.resume(); } } return; } /* * Emit the next record for 'minfile', and invoke ourselves again to * make sure we emit as many records as we can right now. */ rec = streams[minfile].records.shift(); emitRecord(rec.rec, rec.line, opts, stylize); } } /** * Return a function for the given JS code that returns. * * If no 'return' in the given javascript snippet, then assume we are a single * statement and wrap in 'return (...)'. This is for convenience for short * '-c ...' snippets. */ function funcWithReturnFromSnippet(js) { // auto-"return" if (js.indexOf('return') === -1) { if (js.substring(js.length - 1) === ';') { js = js.substring(0, js.length - 1); } js = 'return (' + js + ')'; } // Expose level definitions to condition func context var varDefs = []; Object.keys(upperNameFromLevel).forEach(function (lvl) { varDefs.push(format('var %s = %d;', upperNameFromLevel[lvl], lvl)); }); varDefs = varDefs.join('\n') + '\n'; return (new Function(varDefs + js)); } /** * Parse the command-line options and arguments into an object. * * { * 'args': [...] // arguments * 'help': true, // true if '-h' option given * // etc. * } * * @return {Object} The parsed options. `.args` is the argument list. * @throws {Error} If there is an error parsing argv. */ function parseArgv(argv) { var parsed = { args: [], help: false, color: null, paginate: null, outputMode: OM_LONG, jsonIndent: 2, level: null, strict: false, pids: null, pidsType: null, timeFormat: TIME_UTC // one of the TIME_ constants }; // Turn '-iH' into '-i -H', except for argument-accepting options. var args = argv.slice(2); // drop ['node', 'scriptname'] var newArgs = []; var optTakesArg = {'d': true, 'o': true, 'c': true, 'l': true, 'p': true}; for (var i = 0; i < args.length; i++) { if (args[i].charAt(0) === '-' && args[i].charAt(1) !== '-' && args[i].length > 2) { var splitOpts = args[i].slice(1).split(''); for (var j = 0; j < splitOpts.length; j++) { newArgs.push('-' + splitOpts[j]); if (optTakesArg[splitOpts[j]]) { var optArg = splitOpts.slice(j+1).join(''); if (optArg.length) { newArgs.push(optArg); } break; } } } else { newArgs.push(args[i]); } } args = newArgs; // Expose level definitions to condition vm context var condDefines = []; Object.keys(upperNameFromLevel).forEach(function (lvl) { condDefines.push( format('Object.prototype.%s = %s;', upperNameFromLevel[lvl], lvl)); }); condDefines = condDefines.join('\n') + '\n'; var endOfOptions = false; while (args.length > 0) { var arg = args.shift(); switch (arg) { case '--': endOfOptions = true; break; case '-h': // display help and exit case '--help': parsed.help = true; break; case '--version': parsed.version = true; break; case '--strict': parsed.strict = true; break; case '--color': parsed.color = true; break; case '--no-color': parsed.color = false; break; case '--pager': parsed.paginate = true; break; case '--no-pager': parsed.paginate = false; break; case '-o': case '--output': var name = args.shift(); var idx = name.lastIndexOf('-'); if (idx !== -1) { var indentation = Number(name.slice(idx+1)); if (! isNaN(indentation)) { parsed.jsonIndent = indentation; name = name.slice(0, idx); } } parsed.outputMode = OM_FROM_NAME[name]; if (parsed.outputMode === undefined) { throw new Error('unknown output mode: "'+name+'"'); } break; case '-j': // output with JSON.stringify parsed.outputMode = OM_JSON; break; case '-0': parsed.outputMode = OM_BUNYAN; break; case '-L': parsed.timeFormat = TIME_LOCAL; if (!moment) { throw new Error( 'could not find moment package required for "-L"'); } break; case '--time': var timeArg = args.shift(); switch (timeArg) { case 'utc': parsed.timeFormat = TIME_UTC; break case 'local': parsed.timeFormat = TIME_LOCAL; if (!moment) { throw new Error('could not find moment package ' + 'required for "--time=local"'); } break case undefined: throw new Error('missing argument to "--time"'); default: throw new Error(format('invalid time format: "%s"', timeArg)); } break; case '-p': if (!parsed.pids) { parsed.pids = []; } var pidArg = args.shift(); var pid = +(pidArg); if (!isNaN(pid) || pidArg === '*') { if (parsed.pidsType && parsed.pidsType !== 'num') { throw new Error(format('cannot mix PID name and ' + 'number arguments: "%s"', pidArg)); } parsed.pidsType = 'num'; if (!parsed.pids) { parsed.pids = []; } parsed.pids.push(isNaN(pid) ? pidArg : pid); } else { if (parsed.pidsType && parsed.pidsType !== 'name') { throw new Error(format('cannot mix PID name and ' + 'number arguments: "%s"', pidArg)); } parsed.pidsType = 'name'; parsed.pids = pidArg; } break; case '-l': case '--level': var levelArg = args.shift(); var level = +(levelArg); if (isNaN(level)) { level = +levelFromName[levelArg.toLowerCase()]; } if (isNaN(level)) { throw new Error('unknown level value: "'+levelArg+'"'); } parsed.level = level; break; case '-c': case '--condition': gUsingConditionOpts = true; var condition = args.shift(); if (Boolean(process.env.BUNYAN_EXEC && process.env.BUNYAN_EXEC === 'vm')) { parsed.condVm = parsed.condVm || []; var scriptName = 'bunyan-condition-'+parsed.condVm.length; var code = condDefines + condition; var script; try { script = vm.createScript(code, scriptName); } catch (complErr) { throw new Error(format('illegal CONDITION code: %s\n' + ' CONDITION script:\n' + '%s\n' + ' Error:\n' + '%s', complErr, indent(code), indent(complErr.stack))); } // Ensure this is a reasonably safe CONDITION. try { script.runInNewContext(minValidRecord); } catch (condErr) { throw new Error(format( /* JSSTYLED */ 'CONDITION code cannot safely filter a minimal Bunyan log record\n' + ' CONDITION script:\n' + '%s\n' + ' Minimal Bunyan log record:\n' + '%s\n' + ' Filter error:\n' + '%s', indent(code), indent(JSON.stringify(minValidRecord, null, 2)), indent(condErr.stack) )); } parsed.condVm.push(script); } else { parsed.condFuncs = parsed.condFuncs || []; parsed.condFuncs.push(funcWithReturnFromSnippet(condition)); } break; default: // arguments if (!endOfOptions && arg.length > 0 && arg[0] === '-') { throw new Error('unknown option "'+arg+'"'); } parsed.args.push(arg); break; } } //TODO: '--' handling and error on a first arg that looks like an option. return parsed; } function isInteger(s) { return (s.search(/^-?[0-9]+$/) == 0); } // http://en.wikipedia.org/wiki/ANSI_escape_code#graphics // Suggested colors (some are unreadable in common cases): // - Good: cyan, yellow (limited use), bold, green, magenta, red // - Bad: blue (not visible on cmd.exe), grey (same color as background on // Solarized Dark theme from , see // issue #160) var colors = { 'bold' : [1, 22], 'italic' : [3, 23], 'underline' : [4, 24], 'inverse' : [7, 27], 'white' : [37, 39], 'grey' : [90, 39], 'black' : [30, 39], 'blue' : [34, 39], 'cyan' : [36, 39], 'green' : [32, 39], 'magenta' : [35, 39], 'red' : [31, 39], 'yellow' : [33, 39] }; function stylizeWithColor(str, color) { if (!str) return ''; var codes = colors[color]; if (codes) { return '\033[' + codes[0] + 'm' + str + '\033[' + codes[1] + 'm'; } else { return str; } } function stylizeWithoutColor(str, color) { return str; } /** * Is this a valid Bunyan log record. */ function isValidRecord(rec) { if (rec.v == null || rec.level == null || rec.name == null || rec.hostname == null || rec.pid == null || rec.time == null || rec.msg == null) { // Not valid Bunyan log. return false; } else { return true; } } var minValidRecord = { v: 0, //TODO: get this from bunyan.LOG_VERSION level: INFO, name: 'name', hostname: 'hostname', pid: 123, time: Date.now(), msg: 'msg' }; /** * Parses the given log line and either emits it right away (for invalid * records) or enqueues it for emitting later when it's the next line to show. */ function handleLogLine(file, line, opts, stylize) { if (exiting) { _selfTrace('warn: handleLogLine called while exiting'); return; } currLine = line; // intentionally global // Emit non-JSON lines immediately. var rec; if (!line) { if (!opts.strict) emit(line + '\n'); return; } else if (line[0] !== '{') { if (!opts.strict) emit(line + '\n'); // not JSON return; } else { try { rec = JSON.parse(line); } catch (e) { if (!opts.strict) emit(line + '\n'); return; } } if (!isValidRecord(rec)) { if (!opts.strict) emit(line + '\n'); return; } if (!filterRecord(rec, opts)) return; if (file === null) return emitRecord(rec, line, opts, stylize); return gotRecord(file, line, rec, opts, stylize); } /** * Print out a single result, considering input options. */ function emitRecord(rec, line, opts, stylize) { var short = false; switch (opts.outputMode) { case OM_SHORT: short = true; /* jsl:fall-thru */ case OM_LONG: // [time] LEVEL: name[/comp]/pid on hostname (src): msg* (extras...) // msg* // -- // long and multi-line extras // ... // If 'msg' is single-line, then it goes in the top line. // If 'req', show the request. // If 'res', show the response. // If 'err' and 'err.stack' then show that. if (!isValidRecord(rec)) { return emit(line + '\n'); } delete rec.v; // Time. var time; if (!short && opts.timeFormat === TIME_UTC) { // Fast default path: We assume the raw `rec.time` is a UTC time // in ISO 8601 format (per spec). time = '[' + rec.time + ']'; } else if (!moment && opts.timeFormat === TIME_UTC) { // Don't require momentjs install, as long as not using TIME_LOCAL. time = rec.time.substr(11); } else { var tzFormat; var moTime = moment(rec.time); switch (opts.timeFormat) { case TIME_UTC: tzFormat = TIMEZONE_UTC_FORMATS[short ? 'short' : 'long']; moTime.utc(); break; case TIME_LOCAL: tzFormat = TIMEZONE_LOCAL_FORMATS[short ? 'short' : 'long']; break; default: throw new Error('unexpected timeFormat: ' + opts.timeFormat); }; time = moTime.format(tzFormat); } time = stylize(time, 'none'); delete rec.time; var nameStr = rec.name; delete rec.name; if (rec.component) { nameStr += '/' + rec.component; } delete rec.component; if (!short) nameStr += '/' + rec.pid; delete rec.pid; var level = (upperPaddedNameFromLevel[rec.level] || 'LVL' + rec.level); if (opts.color) { var colorFromLevel = { 10: 'white', // TRACE 20: 'yellow', // DEBUG 30: 'cyan', // INFO 40: 'magenta', // WARN 50: 'red', // ERROR 60: 'inverse', // FATAL }; level = stylize(level, colorFromLevel[rec.level]); } delete rec.level; var src = ''; if (rec.src && rec.src.file) { var s = rec.src; if (s.func) { src = format(' (%s:%d in %s)', s.file, s.line, s.func); } else { src = format(' (%s:%d)', s.file, s.line); } src = stylize(src, 'green'); } delete rec.src; var hostname = rec.hostname; delete rec.hostname; var extras = []; var details = []; if (rec.req_id) { extras.push('req_id=' + rec.req_id); } delete rec.req_id; var onelineMsg; if (rec.msg.indexOf('\n') !== -1) { onelineMsg = ''; details.push(indent(stylize(rec.msg, 'cyan'))); } else { onelineMsg = ' ' + stylize(rec.msg, 'cyan'); } delete rec.msg; if (rec.req && typeof (rec.req) === 'object') { var req = rec.req; delete rec.req; var headers = req.headers; if (!headers) { headers = ''; } else if (typeof (headers) === 'string') { headers = '\n' + headers; } else if (typeof (headers) === 'object') { headers = '\n' + Object.keys(headers).map(function (h) { return h + ': ' + headers[h]; }).join('\n'); } var s = format('%s %s HTTP/%s%s', req.method, req.url, req.httpVersion || '1.1', headers ); delete req.url; delete req.method; delete req.httpVersion; delete req.headers; if (req.body) { s += '\n\n' + (typeof (req.body) === 'object' ? JSON.stringify(req.body, null, 2) : req.body); delete req.body; } if (req.trailers && Object.keys(req.trailers) > 0) { s += '\n' + Object.keys(req.trailers).map(function (t) { return t + ': ' + req.trailers[t]; }).join('\n'); } delete req.trailers; details.push(indent(s)); // E.g. for extra 'foo' field on 'req', add 'req.foo' at // top-level. This *does* have the potential to stomp on a // literal 'req.foo' key. Object.keys(req).forEach(function (k) { rec['req.' + k] = req[k]; }) } if (rec.client_req && typeof (rec.client_req) === 'object') { var client_req = rec.client_req; delete rec.client_req; var headers = client_req.headers; delete client_req.headers; var s = format('%s %s HTTP/%s%s', client_req.method, client_req.url, client_req.httpVersion || '1.1', (headers ? '\n' + Object.keys(headers).map( function (h) { return h + ': ' + headers[h]; }).join('\n') : '')); delete client_req.method; delete client_req.url; delete client_req.httpVersion; if (client_req.body) { s += '\n\n' + (typeof (client_req.body) === 'object' ? JSON.stringify(client_req.body, null, 2) : client_req.body); delete client_req.body; } // E.g. for extra 'foo' field on 'client_req', add // 'client_req.foo' at top-level. This *does* have the potential // to stomp on a literal 'client_req.foo' key. Object.keys(client_req).forEach(function (k) { rec['client_req.' + k] = client_req[k]; }); details.push(indent(s)); } function _res(res) { var s = ''; /* * Handle `res.header` or `res.headers` as either a string or * an object of header key/value pairs. Prefer `res.header` if set, * because that's what Bunyan's own `res` serializer specifies, * because that's the value in Node.js's core HTTP server response * implementation that has all the implicit headers. * * Note: `res.header` (string) typically includes the 'HTTP/1.1 ...' * status line. */ var headerTypes = {string: true, object: true}; var headers; var headersStr = ''; var headersHaveStatusLine = false; if (res.header && headerTypes[typeof (res.header)]) { headers = res.header; delete res.header; } else if (res.headers && headerTypes[typeof (res.headers)]) { headers = res.headers; delete res.headers; } if (headers === undefined) { /* pass through */ } else if (typeof (headers) === 'string') { headersStr = headers.trimRight(); // Trim the CRLF. if (headersStr.slice(0, 5) === 'HTTP/') { headersHaveStatusLine = true; } } else { headersStr += Object.keys(headers).map( function (h) { return h + ': ' + headers[h]; }).join('\n'); } /* * Add a 'HTTP/1.1 ...' status line if the headers didn't already * include it. */ if (!headersHaveStatusLine && res.statusCode !== undefined) { s += format('HTTP/1.1 %s %s\n', res.statusCode, http.STATUS_CODES[res.statusCode]); } delete res.statusCode; s += headersStr; if (res.body !== undefined) { var body = (typeof (res.body) === 'object' ? JSON.stringify(res.body, null, 2) : res.body); if (body.length > 0) { s += '\n\n' + body }; delete res.body; } else { s = s.trimRight(); } if (res.trailer) { s += '\n' + res.trailer; } delete res.trailer; if (s) { details.push(indent(s)); } // E.g. for extra 'foo' field on 'res', add 'res.foo' at // top-level. This *does* have the potential to stomp on a // literal 'res.foo' key. Object.keys(res).forEach(function (k) { rec['res.' + k] = res[k]; }); } if (rec.res && typeof (rec.res) === 'object') { _res(rec.res); delete rec.res; } if (rec.client_res && typeof (rec.client_res) === 'object') { _res(rec.client_res); delete rec.client_res; } if (rec.err && rec.err.stack) { var err = rec.err if (typeof (err.stack) !== 'string') { details.push(indent(err.stack.toString())); } else { details.push(indent(err.stack)); } delete err.message; delete err.name; delete err.stack; // E.g. for extra 'foo' field on 'err', add 'err.foo' at // top-level. This *does* have the potential to stomp on a // literal 'err.foo' key. Object.keys(err).forEach(function (k) { rec['err.' + k] = err[k]; }) delete rec.err; } var leftover = Object.keys(rec); for (var i = 0; i < leftover.length; i++) { var key = leftover[i]; var value = rec[key]; var stringified = false; if (typeof (value) !== 'string') { value = JSON.stringify(value, null, 2); stringified = true; } if (value.indexOf('\n') !== -1 || value.length > 50) { details.push(indent(key + ': ' + value)); } else if (!stringified && (value.indexOf(' ') != -1 || value.length === 0)) { extras.push(key + '=' + JSON.stringify(value)); } else { extras.push(key + '=' + value); } } extras = stylize( (extras.length ? ' (' + extras.join(', ') + ')' : ''), 'none'); details = stylize( (details.length ? details.join('\n --\n') + '\n' : ''), 'none'); if (!short) emit(format('%s %s: %s on %s%s:%s%s\n%s', time, level, nameStr, hostname || '', src, onelineMsg, extras, details)); else emit(format('%s %s %s:%s%s\n%s', time, level, nameStr, onelineMsg, extras, details)); break; case OM_INSPECT: emit(util.inspect(rec, false, Infinity, true) + '\n'); break; case OM_BUNYAN: emit(JSON.stringify(rec, null, 0) + '\n'); break; case OM_JSON: emit(JSON.stringify(rec, null, opts.jsonIndent) + '\n'); break; case OM_SIMPLE: /* JSSTYLED */ // if (!isValidRecord(rec)) { return emit(line + '\n'); } emit(format('%s - %s\n', upperNameFromLevel[rec.level] || 'LVL' + rec.level, rec.msg)); break; default: throw new Error('unknown output mode: '+opts.outputMode); } } function emit(s) { try { stdout.write(s); } catch (writeErr) { _selfTrace('exception from stdout.write:', writeErr) // Handle any exceptions in stdout writing in `stdout.on('error', ...)`. } } /** * Process all input from stdin. * * @params opts {Object} Bunyan options object. * @param stylize {Function} Output stylize function to use. * @param callback {Function} `function ()` */ function processStdin(opts, stylize, callback) { var leftover = ''; // Left-over partial line from last chunk. var stdin = process.stdin; stdin.resume(); stdin.setEncoding('utf8'); stdin.on('data', function (chunk) { var lines = chunk.split(/\r\n|\n/); var length = lines.length; if (length === 1) { leftover += lines[0]; return; } if (length > 1) { handleLogLine(null, leftover + lines[0], opts, stylize); } leftover = lines.pop(); length -= 1; for (var i = 1; i < length; i++) { handleLogLine(null, lines[i], opts, stylize); } }); stdin.on('end', function () { if (leftover) { handleLogLine(null, leftover, opts, stylize); leftover = ''; } callback(); }); } /** * Process bunyan:log-* probes from the given pid. * * @params opts {Object} Bunyan options object. * @param stylize {Function} Output stylize function to use. * @param callback {Function} `function (code)` */ function processPids(opts, stylize, callback) { var leftover = ''; // Left-over partial line from last chunk. /** * Get the PIDs to dtrace. * * @param cb {Function} `function (errCode, pids)` */ function getPids(cb) { if (opts.pidsType === 'num') { return cb(null, opts.pids); } if (process.platform === 'sunos') { execFile('/bin/pgrep', ['-lf', opts.pids], function (pidsErr, stdout, stderr) { if (pidsErr) { warn('bunyan: error getting PIDs for "%s": %s\n%s\n%s', opts.pids, pidsErr.message, stdout, stderr); return cb(1); } var pids = stdout.trim().split('\n') .map(function (line) { return line.trim().split(/\s+/)[0] }) .filter(function (pid) { return Number(pid) !== process.pid }); if (pids.length === 0) { warn('bunyan: error: no matching PIDs found for "%s"', opts.pids); return cb(2); } cb(null, pids); } ); } else { var regex = opts.pids; if (regex && /[a-zA-Z0-9_]/.test(regex[0])) { // 'foo' -> '[f]oo' trick to exclude the 'grep' PID from its // own search. regex = '[' + regex[0] + ']' + regex.slice(1); } var cmd = format('ps -A -o pid,command | grep \'%s\'', // Escape single-quotes to avoid breaking the grep arg quoting // (leading to a possible *code execution*) and backslashes to // avoid undoing that escaping. regex.replace(/\\/g, '\\\\') // JSSTYLED .replace(/'/g, "'\\''")); _selfTrace('exec cmd: %j', cmd); exec(cmd, function (pidsErr, stdout, stderr) { if (pidsErr) { warn('bunyan: error getting PIDs for "%s": %s\n%s\n%s', opts.pids, pidsErr.message, stdout, stderr); return cb(1); } var pids = stdout.trim().split('\n') .map(function (line) { return line.trim().split(/\s+/)[0]; }) .filter(function (pid) { return Number(pid) !== process.pid; }); if (pids.length === 0) { warn('bunyan: error: no matching PIDs found for "%s"', opts.pids); return cb(2); } cb(null, pids); } ); } } getPids(function (errCode, pids) { if (errCode) { return callback(errCode); } var probes = pids.map(function (pid) { if (!opts.level) return format('bunyan%s:::log-*', pid); var rval = [], l; for (l in levelFromName) { if (levelFromName[l] >= opts.level) rval.push(format('bunyan%s:::log-%s', pid, l)); } if (rval.length != 0) return rval.join(','); warn('bunyan: error: level (%d) exceeds maximum logging level', opts.level); cleanupAndExit(1); }).join(','); var argv = ['dtrace', '-Z', '-x', 'strsize=4k', '-x', 'switchrate=10hz', '-qn', format('%s{printf("%s", copyinstr(arg0))}', probes)]; //console.log('dtrace argv: %s', argv); var dtrace = spawn(argv[0], argv.slice(1), // Share the stderr handle to have error output come // straight through. Only supported in v0.8+. {stdio: ['pipe', 'pipe', process.stderr]}); dtrace.on('error', function (e) { if (e.syscall === 'spawn' && e.errno === 'ENOENT') { console.error('bunyan: error: could not spawn "dtrace" ' + '("bunyan -p" is only supported on platforms with dtrace)'); } else { console.error('bunyan: error: unexpected dtrace error: %s', e); } callback(1); }) child = dtrace; // intentionally global function finish(code) { if (leftover) { handleLogLine(null, leftover, opts, stylize); leftover = ''; } callback(code); } dtrace.stdout.setEncoding('utf8'); dtrace.stdout.on('data', function (chunk) { var lines = chunk.split(/\r\n|\n/); var length = lines.length; if (length === 1) { leftover += lines[0]; return; } if (length > 1) { handleLogLine(null, leftover + lines[0], opts, stylize); } leftover = lines.pop(); length -= 1; for (var i = 1; i < length; i++) { handleLogLine(null, lines[i], opts, stylize); } }); if (nodeSpawnSupportsStdio) { dtrace.on('exit', finish); } else { // Fallback (for < v0.8) to pipe the dtrace process' stderr to // this stderr. Wait for all of (1) process 'exit', (2) stderr // 'end', and (2) stdout 'end' before returning to ensure all // stderr is flushed (issue #54). var returnCode = null; var eventsRemaining = 3; function countdownToFinish(code) { returnCode = code; eventsRemaining--; if (eventsRemaining == 0) { finish(returnCode); } } dtrace.stderr.pipe(process.stderr); dtrace.stderr.on('end', countdownToFinish); dtrace.stderr.on('end', countdownToFinish); dtrace.on('exit', countdownToFinish); } }); } /** * Process all input from the given log file. * * @param file {String} Log file path to process. * @params opts {Object} Bunyan options object. * @param stylize {Function} Output stylize function to use. * @param callback {Function} `function ()` */ function processFile(file, opts, stylize, callback) { var stream = fs.createReadStream(file); if (/\.gz$/.test(file)) { stream = stream.pipe(require('zlib').createGunzip()); } // Manually decode streams - lazy load here as per node/lib/fs.js var decoder = new (require('string_decoder').StringDecoder)('utf8'); streams[file].stream = stream; stream.on('error', function (err) { streams[file].done = true; callback(err); }); var leftover = ''; // Left-over partial line from last chunk. stream.on('data', function (data) { if (exiting) { _selfTrace('stop reading file "%s" because exiting', file); stream.destroy(); return; } var chunk = decoder.write(data); if (!chunk.length) { return; } var lines = chunk.split(/\r\n|\n/); var length = lines.length; if (length === 1) { leftover += lines[0]; return; } if (length > 1) { handleLogLine(file, leftover + lines[0], opts, stylize); } leftover = lines.pop(); length -= 1; for (var i = 1; i < length; i++) { handleLogLine(file, lines[i], opts, stylize); } }); stream.on('end', function () { streams[file].done = true; if (leftover) { handleLogLine(file, leftover, opts, stylize); leftover = ''; } else { emitNextRecord(opts, stylize); } callback(); }); } /** * From node async module. */ /* BEGIN JSSTYLED */ function asyncForEach(arr, iterator, callback) { callback = callback || function () {}; if (!arr.length) { return callback(); } var completed = 0; arr.forEach(function (x) { iterator(x, function (err) { if (err) { callback(err); callback = function () {}; } else { completed += 1; if (completed === arr.length) { callback(); } } }); }); }; /* END JSSTYLED */ /** * Cleanup and exit properly. * * Warning: this doesn't necessarily stop processing, i.e. process exit * might be delayed. It is up to the caller to ensure that no subsequent * bunyan processing is done after calling this. * * @param code {Number} exit code. * @param signal {String} Optional signal name, if this was exitting because * of a signal. */ function cleanupAndExit(code, signal) { // Guard one call. if (exiting) { return; } exiting = true; _selfTrace('cleanupAndExit(%s, %s)', code, signal); // Clear possibly interrupted ANSI code (issue #59). if (usingAnsiCodes) { stdout.write('\033[0m'); } // Kill possible dtrace child. if (child) { child.kill(signal); } if (pager) { // Let pager know that output is done, then wait for pager to exit. pager.removeListener('exit', onPrematurePagerExit); pager.on('exit', function onPagerExit(pagerCode) { _selfTrace('pager exit -> process.exit(%s)', pagerCode || code); process.exit(pagerCode || code); }); stdout.end(); } else if (code) { // Non-zero exit: Something is wrong. We are very likely still // processing log records -- i.e. we have open handles -- so we need // a hard stop (aka `process.exit`). _selfTrace('process.exit(%s)', code); process.exit(code); } else { // Zero exit: This should be a "normal" exit, for which we want to // flush stdout/stderr. exeunt.softExit(code); } } //---- mainline process.on('SIGINT', function () { cleanupAndExit(1, 'SIGINT'); }); process.on('SIGQUIT', function () { cleanupAndExit(1, 'SIGQUIT'); }); process.on('SIGTERM', function () { cleanupAndExit(1, 'SIGTERM'); }); process.on('SIGHUP', function () { cleanupAndExit(1, 'SIGHUP'); }); process.on('uncaughtException', function (err) { function _indent(s) { var lines = s.split(/\r?\n/); for (var i = 0; i < lines.length; i++) { lines[i] = '* ' + lines[i]; } return lines.join('\n'); } var title = encodeURIComponent(format( 'Bunyan %s crashed: %s', getVersion(), String(err))); var e = console.error; e('```'); e('* The Bunyan CLI crashed!'); e('*'); if (err.name === 'ReferenceError' && gUsingConditionOpts) { /* BEGIN JSSTYLED */ e('* This crash was due to a "ReferenceError", which is often the result of given'); e('* `-c CONDITION` code that doesn\'t guard against undefined values. If that is'); /* END JSSTYLED */ e('* not the problem:'); e('*'); } e('* Please report this issue and include the details below:'); e('*'); e('* https://github.com/trentm/node-bunyan/issues/new?title=%s', title); e('*'); e('* * *'); e('* platform:', process.platform); e('* node version:', process.version); e('* bunyan version:', getVersion()); e('* argv: %j', process.argv); e('* log line: %j', currLine); e('* stack:'); e(_indent(err.stack)); e('```'); process.exit(1); }); // Early termination of the pager: just stop. function onPrematurePagerExit(pagerCode) { _selfTrace('premature pager exit'); // 'pager' and 'stdout' are intentionally global. pager = null; stdout.end() stdout = process.stdout; cleanupAndExit(pagerCode); } function main(argv) { try { var opts = parseArgv(argv); } catch (e) { warn('bunyan: error: %s', e.message); cleanupAndExit(1); return; } if (opts.help) { printHelp(); return; } if (opts.version) { console.log('bunyan ' + getVersion()); return; } if (opts.pids && opts.args.length > 0) { warn('bunyan: error: can\'t use both "-p PID" (%s) and file (%s) args', opts.pids, opts.args.join(' ')); cleanupAndExit(1); return; } if (opts.color === null) { if (process.env.BUNYAN_NO_COLOR && process.env.BUNYAN_NO_COLOR.length > 0) { opts.color = false; } else { opts.color = process.stdout.isTTY; } } usingAnsiCodes = opts.color; // intentionally global var stylize = (opts.color ? stylizeWithColor : stylizeWithoutColor); // Pager. var paginate = ( process.stdout.isTTY && process.stdin.isTTY && !opts.pids && // Don't page if following process output. opts.args.length > 0 && // Don't page if no file args to process. process.platform !== 'win32' && (nodeVer[0] > 0 || nodeVer[1] >= 8) && (opts.paginate === true || (opts.paginate !== false && (!process.env.BUNYAN_NO_PAGER || process.env.BUNYAN_NO_PAGER.length === 0)))); if (paginate) { var pagerCmd = process.env.PAGER || 'less'; /* JSSTYLED */ assert.ok(pagerCmd.indexOf('"') === -1 && pagerCmd.indexOf("'") === -1, 'cannot parse PAGER quotes yet'); var argv = pagerCmd.split(/\s+/g); var env = objCopy(process.env); if (env.LESS === undefined) { // git's default is LESS=FRSX. I don't like the 'S' here because // lines are *typically* wide with bunyan output and scrolling // horizontally is a royal pain. Note a bug in Mac's `less -F`, // such that SIGWINCH can kill it. If that rears too much then // I'll remove 'F' from here. env.LESS = 'FRX'; } _selfTrace('pager: argv=%j, env.LESS=%j', argv, env.LESS); // `pager` and `stdout` intentionally global. pager = spawn(argv[0], argv.slice(1), // Share the stderr handle to have error output come // straight through. Only supported in v0.8+. {env: env, stdio: ['pipe', 1, 2]}); stdout = pager.stdin; pager.on('exit', onPrematurePagerExit); } // Stdout error handling. (Couldn't setup until `stdout` was determined.) stdout.on('error', function (err) { _selfTrace('stdout error event: %s, exiting=%s', err, exiting); if (exiting) { return; } else if (err.code === 'EPIPE') { cleanupAndExit(0); } else { warn('bunyan: error on output stream: %s', err); cleanupAndExit(1); } }); var retval = 0; if (opts.pids) { processPids(opts, stylize, function (code) { cleanupAndExit(code); }); } else if (opts.args.length > 0) { var files = opts.args; files.forEach(function (file) { streams[file] = { stream: null, records: [], done: false } }); asyncForEach(files, function (file, next) { processFile(file, opts, stylize, function (err) { if (err) { warn('bunyan: %s', err.message); retval += 1; } next(); }); }, function (err) { if (err) { warn('bunyan: unexpected error: %s', err.stack || err); cleanupAndExit(1); } else { cleanupAndExit(retval); } } ); } else { processStdin(opts, stylize, function () { cleanupAndExit(retval); }); } } if (require.main === module) { main(process.argv); } node-bunyan-2.0.5/docs/000077500000000000000000000000001377616564400147105ustar00rootroot00000000000000node-bunyan-2.0.5/docs/bunyan.1000066400000000000000000000150431377616564400162710ustar00rootroot00000000000000.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "BUNYAN" "1" "January 2015" "" "bunyan manual" . .SH "NAME" \fBbunyan\fR \- filter and pretty\-print Bunyan log file content . .SH "SYNOPSIS" \fBbunyan\fR [OPTIONS] . .P \&\.\.\. | \fBbunyan\fR [OPTIONS] . .P \fBbunyan\fR [OPTIONS] \-p PID . .SH "DESCRIPTION" "Bunyan" is \fBa simple and fast a JSON logging library\fR for node\.js services, a one\-JSON\-object\-per\-line log format, and \fBa \fBbunyan\fR CLI tool\fR for nicely viewing those logs\. This man page describes the latter\. . .SS "Pretty\-printing" A bunyan log file is a stream of JSON objects, optionally interspersed with non\-JSON log lines\. The primary usage of bunyan(1) is to pretty print, for example: . .IP "" 4 . .nf $ bunyan foo\.log # or `cat foo\.log | bunyan [2012\-02\-08T22:56:52\.856Z] INFO: myservice/123 on example\.com: My message extra: multi line [2012\-02\-08T22:56:54\.856Z] ERROR: myservice/123 on example\.com: My message \.\.\. . .fi . .IP "" 0 . .P By default the "long" output format is used\. Use the \fB\-o FORMAT\fR option to emit other formats\. E\.g\.: . .IP "" 4 . .nf $ bunyan foo\.log \-o short 22:56:52\.856Z INFO myservice: My message extra: multi line 22:56:54\.856Z ERROR myservice: My message \.\.\. . .fi . .IP "" 0 . .P These will color the output if supported in your terminal\. See "OUTPUT FORMATS" below\. . .SS "Filtering" The \fBbunyan\fR CLI can also be used to filter a bunyan log\. Use \fB\-l LEVEL\fR to filter by level: . .IP "" 4 . .nf $ bunyan foo\.log \-l error # show only \'error\' level records [2012\-02\-08T22:56:54\.856Z] ERROR: myservice/123 on example\.com: My message . .fi . .IP "" 0 . .P Use \fB\-c COND\fR to filter on a JavaScript expression returning true on the record data\. In the COND code, \fBthis\fR refers to the record object: . .IP "" 4 . .nf $ bunyan foo\.log \-c `this\.three` # show records with the \'extra\' field [2012\-02\-08T22:56:52\.856Z] INFO: myservice/123 on example\.com: My message extra: multi line . .fi . .IP "" 0 . .SH "OPTIONS" . .TP \fB\-h\fR, \fB\-\-help\fR Print this help info and exit\. . .TP \fB\-\-version\fR Print version of this command and exit\. . .TP \fB\-q\fR, \fB\-\-quiet\fR Don\'t warn if input isn\'t valid JSON\. . .P Dtrace options (only on dtrace\-supporting platforms): . .TP \fB\-p PID\fR, \fB\-p NAME\fR Process bunyan:log\-* probes from the process with the given PID\. Can be used multiple times, or specify all processes with \'*\', or a set of processes whose command & args match a pattern with \'\-p NAME\'\. . .P Filtering options: . .TP \fB\-l\fR, \fB\-\-level LEVEL\fR Only show messages at or above the specified level\. You can specify level \fInames\fR or numeric values\. (See \'Log Levels\' below\.) . .TP \fB\-c COND\fR, \fB\-\-condition COND\fR Run each log message through the condition and only show those that resolve to a truish value\. E\.g\. \fB\-c \'this\.pid == 123\'\fR\. . .TP \fB\-\-strict\fR Suppress all but legal Bunyan JSON log lines\. By default non\-JSON, and non\-Bunyan lines are passed through\. . .P Output options: . .TP \fB\-\-color\fR Colorize output\. Defaults to try if output stream is a TTY\. . .TP \fB\-\-no\-color\fR Force no coloring (e\.g\. terminal doesn\'t support it) . .TP \fB\-o FORMAT\fR, \fB\-\-output FORMAT\fR Specify an output format\. One of \fBlong\fR (the default), \fBshort\fR, \fBjson\fR, \fBjson\-N\fR, \fBbunyan\fR (the native bunyan 0\-indent JSON output) or \fBinspect\fR\. . .TP \fB\-j\fR Shortcut for \fB\-o json\fR\. . .TP \fB\-L\fR, \fB\-\-time local\fR Display the time field in \fIlocal\fR time, rather than the default UTC time\. . .SH "LOG LEVELS" In Bunyan log records, then \fBlevel\fR field is a number\. For the \fB\-l|\-\-level\fR argument the level \fBnames\fR are supported as shortcuts\. In \fB\-c|\-\-condition\fR scripts, uppercase symbols like "DEBUG" are defined for convenience\. . .IP "" 4 . .nf Level Name Level Number Symbol in COND Scripts trace 10 TRACE debug 20 DEBUG info 30 INFO warn 40 WARN error 50 ERROR fatal 60 FATAL . .fi . .IP "" 0 . .SH "OUTPUT FORMATS" . .nf FORMAT NAME DESCRIPTION long (default) The default output\. Long form\. Colored and "pretty"\. \'req\' and \'res\' and \'err\' fields are rendered specially as an HTTP request, HTTP response and exception stack trace, respectively\. For backward compat, the name "paul" also works for this\. short Like the default output, but more concise\. Some typically redundant fields are ellided\. json JSON output, 2\-space indentation\. json\-N JSON output, N\-space indentation, e\.g\. "json\-4" bunyan Alias for "json\-0", the Bunyan "native" format\. inspect Node\.js `util\.inspect` output\. . .fi . .SH "DTRACE SUPPORT" On systems that support DTrace (e\.g\., MacOS, FreeBSD, illumos derivatives like SmartOS and OmniOS), Bunyan will create a DTrace provider (\fBbunyan\fR) that makes available the following probes: . .IP "" 4 . .nf log\-trace log\-debug log\-info log\-warn log\-error log\-fatal . .fi . .IP "" 0 . .P Each of these probes has a single argument: the string that would be written to the log\. Note that when a probe is enabled, it will fire whenever the corresponding function is called, even if the level of the log message is less than that of any stream\. . .P See \fIhttps://github\.com/trentm/node\-bunyan#dtrace\-support\fR for more details and the \'\-p PID\' option above for convenience usage\. . .SH "ENVIRONMENT" . .TP \fBBUNYAN_NO_COLOR\fR Set to a non\-empty value to force no output coloring\. See \'\-\-no\-color\'\. . .SH "PROJECT & BUGS" \fBbunyan\fR is written in JavaScript and requires node\.js (\fBnode\fR)\. The project lives at \fIhttps://github\.com/trentm/node\-bunyan\fR and is published to npm as "bunyan"\. . .IP "\(bu" 4 README, Install notes: \fIhttps://github\.com/trentm/node\-bunyan#readme\fR . .IP "\(bu" 4 Report bugs to \fIhttps://github\.com/trentm/node\-bunyan/issues\fR\. . .IP "\(bu" 4 See the full changelog at: \fIhttps://github\.com/trentm/node\-bunyan/blob/master/CHANGES\.md\fR . .IP "" 0 . .SH "LICENSE" MIT License (see \fIhttps://github\.com/trentm/node\-bunyan/blob/master/LICENSE\.txt\fR) . .SH "COPYRIGHT" node\-bunyan is Copyright (c) 2012 Joyent, Inc\. Copyright (c) 2012 Trent Mick\. All rights reserved\. node-bunyan-2.0.5/docs/bunyan.1.html000066400000000000000000000267711377616564400172460ustar00rootroot00000000000000 bunyan(1) - filter and pretty-print Bunyan log file content
  1. bunyan(1)
  2. bunyan manual
  3. bunyan(1)

NAME

bunyan - filter and pretty-print Bunyan log file content

SYNOPSIS

bunyan [OPTIONS]

... | bunyan [OPTIONS]

bunyan [OPTIONS] -p PID

DESCRIPTION

"Bunyan" is a simple and fast a JSON logging library for node.js services, a one-JSON-object-per-line log format, and a bunyan CLI tool for nicely viewing those logs. This man page describes the latter.

Pretty-printing

A bunyan log file is a stream of JSON objects, optionally interspersed with non-JSON log lines. The primary usage of bunyan(1) is to pretty print, for example:

$ bunyan foo.log          # or `cat foo.log | bunyan
[2012-02-08T22:56:52.856Z]  INFO: myservice/123 on example.com: My message
    extra: multi
    line
[2012-02-08T22:56:54.856Z] ERROR: myservice/123 on example.com: My message
...

By default the "long" output format is used. Use the -o FORMAT option to emit other formats. E.g.:

$ bunyan foo.log -o short
22:56:52.856Z  INFO myservice: My message
    extra: multi
    line
22:56:54.856Z ERROR myservice: My message
...

These will color the output if supported in your terminal. See "OUTPUT FORMATS" below.

Filtering

The bunyan CLI can also be used to filter a bunyan log. Use -l LEVEL to filter by level:

$ bunyan foo.log -l error       # show only 'error' level records
[2012-02-08T22:56:54.856Z] ERROR: myservice/123 on example.com: My message

Use -c COND to filter on a JavaScript expression returning true on the record data. In the COND code, this refers to the record object:

$ bunyan foo.log -c `this.three`     # show records with the 'extra' field
[2012-02-08T22:56:52.856Z]  INFO: myservice/123 on example.com: My message
    extra: multi
    line

OPTIONS

-h, --help

Print this help info and exit.

--version

Print version of this command and exit.

-q, --quiet

Don't warn if input isn't valid JSON.

Dtrace options (only on dtrace-supporting platforms):

-p PID, -p NAME
Process bunyan:log-* probes from the process with the given PID. Can be used multiple times, or specify all processes with '*', or a set of processes whose command & args match a pattern with '-p NAME'.

Filtering options:

-l, --level LEVEL

Only show messages at or above the specified level. You can specify level names or numeric values. (See 'Log Levels' below.)

-c COND, --condition COND

Run each log message through the condition and only show those that resolve to a truish value. E.g. -c 'this.pid == 123'.

--strict

Suppress all but legal Bunyan JSON log lines. By default non-JSON, and non-Bunyan lines are passed through.

Output options:

--color

Colorize output. Defaults to try if output stream is a TTY.

--no-color

Force no coloring (e.g. terminal doesn't support it)

-o FORMAT, --output FORMAT

Specify an output format. One of long (the default), short, json, json-N, bunyan (the native bunyan 0-indent JSON output) or inspect.

-j

Shortcut for -o json.

-L, --time local

Display the time field in local time, rather than the default UTC time.

LOG LEVELS

In Bunyan log records, then level field is a number. For the -l|--level argument the level names are supported as shortcuts. In -c|--condition scripts, uppercase symbols like "DEBUG" are defined for convenience.

Level Name      Level Number    Symbol in COND Scripts
trace           10              TRACE
debug           20              DEBUG
info            30              INFO
warn            40              WARN
error           50              ERROR
fatal           60              FATAL

OUTPUT FORMATS

FORMAT NAME         DESCRIPTION
long (default)      The default output. Long form. Colored and "pretty".
                    'req' and 'res' and 'err' fields are rendered specially
                    as an HTTP request, HTTP response and exception
                    stack trace, respectively. For backward compat, the
                    name "paul" also works for this.
short               Like the default output, but more concise. Some
                    typically redundant fields are ellided.
json                JSON output, 2-space indentation.
json-N              JSON output, N-space indentation, e.g. "json-4"
bunyan              Alias for "json-0", the Bunyan "native" format.
inspect             Node.js `util.inspect` output.

DTRACE SUPPORT

On systems that support DTrace (e.g., MacOS, FreeBSD, illumos derivatives like SmartOS and OmniOS), Bunyan will create a DTrace provider (bunyan) that makes available the following probes:

log-trace
log-debug
log-info
log-warn
log-error
log-fatal

Each of these probes has a single argument: the string that would be written to the log. Note that when a probe is enabled, it will fire whenever the corresponding function is called, even if the level of the log message is less than that of any stream.

See https://github.com/trentm/node-bunyan#dtrace-support for more details and the '-p PID' option above for convenience usage.

ENVIRONMENT

BUNYAN_NO_COLOR
Set to a non-empty value to force no output coloring. See '--no-color'.

PROJECT & BUGS

bunyan is written in JavaScript and requires node.js (node). The project lives at https://github.com/trentm/node-bunyan and is published to npm as "bunyan".

LICENSE

MIT License (see https://github.com/trentm/node-bunyan/blob/master/LICENSE.txt)

node-bunyan is Copyright (c) 2012 Joyent, Inc. Copyright (c) 2012 Trent Mick. All rights reserved.

  1. January 2015
  2. bunyan(1)
Fork me on GitHub node-bunyan-2.0.5/docs/bunyan.1.ronn000066400000000000000000000136461377616564400172530ustar00rootroot00000000000000# bunyan(1) -- filter and pretty-print Bunyan log file content ## SYNOPSIS `bunyan` \[OPTIONS\] ... | `bunyan` \[OPTIONS\] `bunyan` \[OPTIONS\] -p PID ## DESCRIPTION "Bunyan" is **a simple and fast a JSON logging library** for node.js services, a one-JSON-object-per-line log format, and **a `bunyan` CLI tool** for nicely viewing those logs. This man page describes the latter. ### Pretty-printing A bunyan log file is a stream of JSON objects, optionally interspersed with non-JSON log lines. The primary usage of bunyan(1) is to pretty print, for example: $ bunyan foo.log # or `cat foo.log | bunyan [2012-02-08T22:56:52.856Z] INFO: myservice/123 on example.com: My message extra: multi line [2012-02-08T22:56:54.856Z] ERROR: myservice/123 on example.com: My message ... By default the "long" output format is used. Use the `-o FORMAT` option to emit other formats. E.g.: $ bunyan foo.log -o short 22:56:52.856Z INFO myservice: My message extra: multi line 22:56:54.856Z ERROR myservice: My message ... These will color the output if supported in your terminal. See "OUTPUT FORMATS" below. ### Filtering The `bunyan` CLI can also be used to filter a bunyan log. Use `-l LEVEL` to filter by level: $ bunyan foo.log -l error # show only 'error' level records [2012-02-08T22:56:54.856Z] ERROR: myservice/123 on example.com: My message Use `-c COND` to filter on a JavaScript expression returning true on the record data. In the COND code, `this` refers to the record object: $ bunyan foo.log -c `this.three` # show records with the 'extra' field [2012-02-08T22:56:52.856Z] INFO: myservice/123 on example.com: My message extra: multi line ## OPTIONS * `-h`, `--help`: Print this help info and exit. * `--version`: Print version of this command and exit. * `-q`, `--quiet`: Don't warn if input isn't valid JSON. Dtrace options (only on dtrace-supporting platforms): * `-p PID`, `-p NAME`: Process bunyan:log-\* probes from the process with the given PID. Can be used multiple times, or specify all processes with '\*', or a set of processes whose command & args match a pattern with '-p NAME'. Filtering options: * `-l`, `--level LEVEL`: Only show messages at or above the specified level. You can specify level *names* or numeric values. (See 'Log Levels' below.) * `-c COND`, `--condition COND`: Run each log message through the condition and only show those that resolve to a truish value. E.g. `-c 'this.pid == 123'`. * `--strict`: Suppress all but legal Bunyan JSON log lines. By default non-JSON, and non-Bunyan lines are passed through. Output options: * `--color`: Colorize output. Defaults to try if output stream is a TTY. * `--no-color`: Force no coloring (e.g. terminal doesn't support it) * `-o FORMAT`, `--output FORMAT`: Specify an output format. One of `long` (the default), `short`, `json`, `json-N`, `bunyan` (the native bunyan 0-indent JSON output) or `inspect`. * `-j`: Shortcut for `-o json`. * `-L`, `--time local`: Display the time field in *local* time, rather than the default UTC time. ## LOG LEVELS In Bunyan log records, then `level` field is a number. For the `-l|--level` argument the level **names** are supported as shortcuts. In `-c|--condition` scripts, uppercase symbols like "DEBUG" are defined for convenience. Level Name Level Number Symbol in COND Scripts trace 10 TRACE debug 20 DEBUG info 30 INFO warn 40 WARN error 50 ERROR fatal 60 FATAL ## OUTPUT FORMATS FORMAT NAME DESCRIPTION long (default) The default output. Long form. Colored and "pretty". 'req' and 'res' and 'err' fields are rendered specially as an HTTP request, HTTP response and exception stack trace, respectively. For backward compat, the name "paul" also works for this. short Like the default output, but more concise. Some typically redundant fields are ellided. json JSON output, 2-space indentation. json-N JSON output, N-space indentation, e.g. "json-4" bunyan Alias for "json-0", the Bunyan "native" format. inspect Node.js `util.inspect` output. ## DTRACE SUPPORT On systems that support DTrace (e.g., MacOS, FreeBSD, illumos derivatives like SmartOS and OmniOS), Bunyan will create a DTrace provider (`bunyan`) that makes available the following probes: log-trace log-debug log-info log-warn log-error log-fatal Each of these probes has a single argument: the string that would be written to the log. Note that when a probe is enabled, it will fire whenever the corresponding function is called, even if the level of the log message is less than that of any stream. See for more details and the '-p PID' option above for convenience usage. ## ENVIRONMENT * `BUNYAN_NO_COLOR`: Set to a non-empty value to force no output coloring. See '--no-color'. ## PROJECT & BUGS `bunyan` is written in JavaScript and requires node.js (`node`). The project lives at and is published to npm as "bunyan". * README, Install notes: * Report bugs to . * See the full changelog at: ## LICENSE MIT License (see ) ## COPYRIGHT node-bunyan is Copyright (c) 2012 Joyent, Inc. Copyright (c) 2012 Trent Mick. All rights reserved. node-bunyan-2.0.5/docs/img/000077500000000000000000000000001377616564400154645ustar00rootroot00000000000000node-bunyan-2.0.5/docs/img/bunyan.browserify.png000066400000000000000000006040601377616564400216660ustar00rootroot00000000000000PNG  IHDR  iCCPICC ProfileH WgXSSzW5TFHBJ bG\ *"`CWE\ kA,*wEݻw<3=}|3gT:Ri@$GϞĦryRϻ!UlHlNg2!> r LgH bM  AMlt(Ps2!ȳsyBhG/@<boˇxc23gxĖ)?ܔo6\7<4:gK3s?Ufk15=;=&Y0oyܠ!^%p"]a8 &\C3yzz>3<ڒk@l'|.y؄aM_1\Ed(;7fLѧqÈVB xRAF9QDvIᱠSe? K&ņBƌb #*@ cDW1a_,V&&` q@G @"0'X\ $A5L:BzB$=$]#)HF8sXĀC>?U޸'k_Xp7}g*}mmG -~Fpo=S0x 3 QUu}} r 9L–c֊;5`mq5 "+DA8̢$#%7Űkg {I@:l?VP=Am 7uq/gc[ fs ؂l%<΁k ̔Α-c o ^B.|粚p/' \>;gp Se GP\NKUt<CT{Ԩjjj\jjGn3#3WUoUA0k,ءqZc0<NYf&YBY_]WKCI+^kVq- c8 jAu֧QF FU3ڣ}څڵ״?utuuu'ݢ{VghѼхYEۡצקo/ߤZǀekfA!Plsۏ.eaɍ[37i6554`:ϴLdѬ콹y2zgbiugL?>CuwơdRrBHn%/R m^bTgB/:aGT"įBӶOLߝ>QyT!Ii0sN@ڐ+ Fe7hCnRAwnyYV-6zΊ9O~mg4oo_,HYмd҅]BYL[|K4._h食B~.P)Xlr|xy V|-^(+*)?<*uUj[֐H\_vOzq^uխg/\vÌ %N%[767*J#J6nZsZym^Ŋ/oRUkOnn^Wi^Y#wǓ;[~qj]_vKv+D9SZUWojZ^ݽo꾎jlkײjM͇6;\qyS[/W4$6t ;x䷱>ft'h'8wIsJxQ;'zfҙgϟ >wůyZ=Z^pPb]sۑߝ?^wRC{Gc}.xUՋ&^w捩777ʸv;SWr_~V*\>h{#ޣZӪgώuww}liϔϥ_4~ zw s`@ʕqT^Dxv2t7T C9BOD @恁/;MYCw2BMmjm_x`vA pHYs%%IR$iTXtXML:com.adobe.xmp 1568 482 @IDATx] \G_ 'VZV>OZﯭԶV;ׇW=>VKU (x!bv7ل$@~;;;3͗_~;z^" " " " @k8sDDmk -" " " " ":dvHz>ygw8\թㄕ=;.qL*VTX(DEN[tOpl̺X-A!"XZI,GL+qgD@D@D@D!P__T*;(pAL](1#" " " .)yG#"b`" " " "`:ʆccLDbΈvFQf3"cb?3rCV" " @#˗:\ " [9<{ם " "tEjkkQ LPP3" &3D@D@DcK66-|wl1tQ@Lݲ1ӈ" " @Ehvl jFpDV" " "СжLhCK{' ]5vv5oD@D@D4(hkm'*:IAfE6( " " @B^.T/+`|elw"r@D@D@@)tRAݭC&!_r9?" " "Ѕ7h(&tMx!W^~j޲ISED@ɛ7ɍϷ= 3CBtFu?d'"Xuuu Mzޞ>0wG888ڢMEtt/eMq̭U!@F}`U EW0Y^d悂7KKJ` ˯W~{+_У8gL0""!|W 1lmƹC=MOlΝSN0>Rv1fΜ/So}]@Æ?~M\DUCG:`%ܹ~(m}`'ݧh#f\Ԇ?mDG'| d6rfwʸ'AE);;Mח}DG7Df]j;p= È1?V"|}!{ee̡aBɊcP侧=t1u7\Ϟ7oݑ>>jMO[/̝%`bPJ pүWn`炻wݾuE + t oqt.zpV:$$1n&"rЦ&XG' j;џVQ-:yoi);K?1q [H Mo<===`<Jvi12 -cj<1";"FEjE,2I<#"fX. 8pqnn~TUVrD˪Ц&a={夹\*AnE|>aa]U;o%Jhgu&Xڎ>"n G]d0 E]X;zYn}lJO+EN>^[07J-N:S` CJ~-'yz/qӴK Wp)jE2>ӣT05j ӴK $ 1RvDŽ, &nJ缔J{LzI}]^bH7Z"nW| "'eCA"s&N-ƚ/1}\B( &0 vI6i٩z9zr ӾfE5PT\{Mk ۺquBl* d;YR$=ԣ?".=8tqm"7gEYթ"'歿7Cz7usQ#Ckn+gӃ􁽺=E=+eKgϝcdAh U}zq,>~]#=& & V=n " E΄QE }2ܯD'B 41A^uݺꈰB Sg+1:7G0grڹ9{z"('DYllb!gqz]b9JiVQ,Qy+a^f˷qu=1̿vIYmYġ.edI1S7dΊd>?s[\VԱP+5$]mvv_|1 pL>&m5>%XiELCJ%{x[,g(.F!g{zUujD$4, fr|t6χ6M5='%lCqo.*&:mj{SQxF!%{زh⺫ (V"-kۼi}6=--N]c`\\P[ ];똼{y~\j*(ytha/pd Z+sCep8Zm{Zt?)>?^r&VC]*l DӆrFrzXLgl\q:iG[VKqK/yQ~bє4RV{Famaٔ7l}Vc5'id58χELO?}X\VFj8QQRmo[.qٳs LMw*ȣL 砸qsØGt@VnڈM|@IP|iOz/S/g=}\9C5֡nX/|'G Zj!LЈ[fX`N7z Gh:,8F53+33;+ׯI;V۱cš6Pt+.wMv_{4tLvIdk ( uL]B bˢ텯WJ␃K;򫤝kGêvz둲5ט;;O lg4|rwVV`wvv\9qO'XpMp>=VܡgωVkX[\Bi@M6s-嶱IlK.c75.ֶ\,˿2%K`5V>8a.0 F.r:޷o_^c"o=q#k\[ϦcboMM&A)0`,=ǭj<v_ʽ]ZM;;y$de VrIΥ{ `XF4Yمzx u )^˽}ZA|K?lTw #RYiB.{)zy 7|Ht\a|'#~|MߖeM}OF˽={;zɅUUǮqa`[r5f oV.0H`T4V*+jO6!۱}|Hۨ-A)`O( } }qe}qUoZaYDWEL7MS04J&pJXҫG­dHYg74a^pB.KdJN[-R&K_۠8?RQ>Ьbi |֥Ri=9(X$Z] f6#,h yJS&*UE@D28˝))~K9fUbĹٗnA5#}B9D0`BngqC+%ӿfv |->ꞂCB|<+Р{##)2"ɾR@6jN>1#i#CQ(),xoSws/^/,'W J)%b\e\.w$iDHfg_!2At]JVmNH cqQA=iuBaqeĐPJ׮_wvǟ3"GZF (٬,|qLJ%[;;V^-7MS0A.r&I1H~lu7<,A eTJ2ȋVFm,Z\\\,P Vd6vϟ%h>rlp%'G[a,D"I=p쁄r <#̸k޶zxxM~?| m,`g]i{gM.ӛ[*⒒/}icG?9r 7%??b\ۿxL=iaM0{Sɜdg&7/VM2~ЕW| Swx0&Mo%i72IE_'Ŧ.qښ iKxױ>I{I iOA+p[„9hJQDgoyk88w'Mxe:1L7z/ ٣K&y{1sw511oA?m)~>KAC_u _Oq y=3TqQpsgizpXh?ӄ4nXq >{k<@W1;Ƽ$E 6&$CCl oœ=bŻ$&sѣ22N3 9ti1id5ibF:u3R%`-pܼm}?u8jӫWϘ@IV|;aJ*0 A ^]s&дXj;knԬfY˸׵ Nx9ݠ-6Vy' }p ƌ֯ڇːKJd@ n1?#!iIB{}o\}]9lsj) IrRWYI3cۼbz!dYv JXGz9ǻ r*%\۝2K[4+&}]L IڃgceĐ|;S82P,-e_4X>%ahg@(Uw^p8s:]fݕ.on^Lc#"$~>H*| -wǒ l ZuuEy򽗖#(!H z %.'}\׀Ai ȁ<ryHZ%9gO?U) 74:S;գgƵjlZڥ8e]k׮59UU}w8U˾ڳtlo_Az:,etPV`foXMw})nPiD-N\{~ H$RENO^잠 xqB8fg4fڥىcH~U`׀(0JyJ'eK1˗%NV#[]7XʼnY?1^{O:8(_M>j }Bfʚ#ѓ<_]"GJ/6hZcUZbTiءxߦ0DU cYWuT=!twXV@%Κ &BHfx( ;TjYeU'{T ?=>2YhInMsslًOH24.<,h>MjPu48svFk?7j8Ș D!SY5 m FtR3v %4>!h֬YϘ78}QQi3ϱ̨]Lz=aq_?9$2jI}~qgIW4mmʪ9gEA d#Uyd%F=X. kͯl4,?~5zkȮ|#[rJ{˱oO[&/VCp{0+ޓ'"o,;Iou?`y$4բH:Ԅtfh\Lɻۜ=<&yW=m1CMd꫆d Z}݉3ՎٓQN)ښE6_=IZR1mvVK.c(ڏɛ?xC:e3J=t"^چ) tyW|긘 ,rմ Ed~;͢;0"2Ypr%0W1=F,YNM~P^#Ak_Ĕ5bqCcf>,ߓ%7/z,\r۳ց2++yq:,f}Ŋ(y'}zx wSxv׿g#˨&d$c (ڥL@eV`o|(A.LSuCfxXI:ѻo@0JH{Qr&j-S^ϕ9StRaSlφar-il3?C7dyN%bBS&9U]sz+F17-73`՛kw ͜@3v6 \"O`if.i8w}}D זK4#ՂmhP視].g%'e{p-m M%ߦb-ѶβGQfm9a&,"{XjTDkZ@j DھӹX[ i ׽lN~uEdMrH % -MN4/EyXCk'A &ĠܪX KZǤdl=Z}Jߔ/Sh/BW>\c9hY$2m.ہ! $u$1}yqMDՕ=`KRվyq S% hw&̮/kGgmj29٘âCi)}E؎wi7sCR 7<4ݶ.oM/7`[36#%Kˆӭ/x3VϏjXCm;,OKϮaf Op%M\GKkKM++e۶]ooJT&ma?WQ%:rчL硛Y 6Da~`ϛ9~Xsg޴*1v윔<:iɯ M"E1SVƓ~GkH J,؄/]b{'y{8A4I{.M;p&K@.%KR{HճBLfK$(pa6 /C>9%?j ;3ӋFAj_M N'?ڭCiLVtU`Nei4͛MQD$_)ֲլ\G=T歺ydEޞ]fBl-ޙrέWr]:87S":41%殓hO-zݼCQgY&prqӨ'7gwksFaՒ3v۟}ã؉=CN@`Vʚ AdӇ1jW3JpT$ #D6pd _By)K6.R-T/&i4 +_zKɘYH@ha %c)$-~gfi zu`)Ti0XTR/=qV ,)#/r_Hzg]\]!r7Aޭ#TQ rSe<3cCZ&c|J(["3giPSL?fnTU3q<ņ) e6#0x'?vp.A.?}hÒtv&\(9FEԒ~;kc >}7nr| ;OWhm]iD<_>Q=U>E>\ qiT3N6V{P8z~OpGfiG7aOKJ^5l2UC!޶ct;(Z>\3=?b`6]! ,Ťczj1X[|b,XDvش!0]|J!A4 Y3Fzm@NgDk𼘝y>f@&r hDEE8bBB 'ٕ>ZB&Z0'6lßJv;kBW+bF_:_-&RӋAy$ʍ>?4=̎WMfgvYp2Cf$_͂8^ό+..3[SZ\nhg)t MsLF3*T*uw0̴CZǍ=VjMβ|26fX1򴿵`v/KLW~ټŗ~<'+ 푭['sJ&8PC.J]VMI(BFkVTwalZos 5ʪll:n֦e|tJvIq"Fs$tќĮSrE\J=^W|y%7>f@ gUoE/rj*iEmȫh zI+bvfT&n6:Q0ws3Arbfqu~}yaߜ\nKͅҰKVqy?5#20Lʌtff~-\Rmm͛O3w^tz%b>~p0E#&@L^.SrYhLBOع&uGElU\ۻ YlcU[[[QSWS&l҆z.)Q}XD;(bJߦ*N0.\O| ?CdAh~`N$I]V"Ig%߽4Z X8,qfwK*c֩ex;>ے &*̊_ s0mЪ1m;rX, W u9rR3eR7,Z&[tXaaRC)ynJDרVIj ߼%%u?ϰfp4Ɇ]at0G\p^+F^ Vawl&khh ߬Xb:]];ZQ=W.8,dͬ`eʜ[cU ӻ/O.QfD#V;3}m0ӕDga [:G8WMX#M" #_͘e9*#@AwzFdoq19 LɐMvCw+N,Xh{>̟__\B6O Ռ=q9kTsd\F}]٣_4:P[٧wݯW/&Xu!u.ľ^pj5(C'.~yF?LJ:Ge6#׮]c`#=z hKC)n󒏅2|DM ˀoO~JzB ӺRǥ-#2賸UqÆHj&u UlҪtՓg1u"9P[c{ -յC:A:񇻁 ש_7K[Dq0p =★ϲ/Vb~}pR|SO8O*@½چ0z$"u83}xohHwCD#Љ`3oRLxSY:&.}sO?% nݠmg7]2 &0O/4aANͰd™q`Y{`Rsze3gPŤ>8/v KUo-s3AT#*U@990˿W]jN|xAq3O1~|qrg}GIé9gO5MRDCE_$j PYu;ͼi#LhO{;Cѩ.MV%y+VCBa@e&<#,wbCt?XD^RlQj-JD֪h'r Sl{zWT&{ynd{K4;~R<nO5 vyצ)̣-󂫙\e=M] +T,75_x= nWv}t{R {t{4@S6reD+?6b+,^d j`.5q` ȇ?̪ O,'LەN~5MԚUu9󎈡yQG#Ttzn]6VVdf@ +O|lScrv@ v iYoiTs_Q||M)TL5Tz & F;5j1C&=kguu1:E4{x~c(ȼUWV4 ##2^^rZٿ瞟>jMҴ' .1y8 e P0A fX,ᒝޱq+ٛgRx\}8*1=RʤΡD p_')һ9Gw>j sr[ }F/ZKīW+NZF4Yu-]@)8*iKۻK-z- uNK46+>?΍7UQH里F{lg|DT cΌͧHJ*h[}\>}5>dTN&|hm\s<4tdFlBhι|94A+ހ8@|X:ibbbXqRltIi1/:Ŗ  &p>+IΥ?|:׽*',]fLH \b7'4amV4bJ#6gԄUo*q„1$ZgsJ҇ͩ]eMKTRrm4%=aa\YarF$g^|7Mӛت$\8l$*! IRINY^,ꗟ.*,zM8'?(.+#}-ã7vjuϡ'>rYq6~9='V}u+ʞ)U4ıJrIqΎU/У̯ L:(oۢLJT|'Y^4}[ڿN. |"aGggY:]-AW$kFb 3f]߅趋 Hvn $o]U쾄ӯZ(+pA̗bF[$sZ+<"O>IÑgLJ3V/ O=+*Fkcq*C[ozo9c|rxZ%ZVSO#-uh ~3`og(֬0a㫯η?Jr Cwf`QV؛A3[8/f)8mYBKT9?E#W,->(͙KxרA$Ǒ+'Br3 Z6}(8xAp }|(A:t3#3?_~#dBctvfI~gn cF)g$i}|}O8Y" MGxB󆑯$s39j 5RrF  wmQyrc],PJ^i@ 7$PIi,NJ*i uadûj5{I<1y8:0ɗ);a>ܶi8h,vvܻWzZ.t{X¡XggT*Hn`gp~OpS $g+L||,Y%Mɏfzs'T/uRj UgCM[ܪ*j ˴5E?>.nЀ.F^ϧHeP+9͋C KW1{]$ٱ뀽/Ι)+$odz+fy94q#Ɏ,[Ύ^МGwQٻvdŞ u@[M"='Ip1Z[,=`YLV~%`K}< 3w$Cb V;wVo~cf8N&&S'J)ồV>+vZU8FW(ў%*㊏?ą By[*Rt`2ʎrm!5@פi4ClP-t?@U%?,|bLOhq\\lN=7mTӚA' !SfY6Ԙ>Å QZ]_ m43cR-qq3ybYTA芃gM=[OO5湩QW_&$H'lF316B%jb}{LZiRdUG@! %Ed;˼ws)5CwUh\@^3{uvl^3auMIWﳴjyힽ{F7b+sS@IDATě|ǎ-(*dX9 a@?nÃ׿Ѩ]l|-U|4GPga9{zsb>]7odMB^@᤹sSL1)Gsˆ>B)Ψ/Qb«䳸6?_@Q5'l|bz6h$]898 $Kܳ~"P}x:RQV9`Tb60s;EI3 l-Run?\5k 9Z-6s…OQ/wp?u0aX yR8#R}kSJrhꡥhxXӶ\ؠZSϓ%K xnEk/z D?Jx,5wtd|ռk3Ʒ+bF3aDWy4oK[Aa!Ւ/)wZAKXxlSp)`?]2>Xl[=f"td5MbSc)r]-VdjMdZ^)r|Xkm/].;%prs}@Q-r3;A͔Õ MfFp?.l!#kEx"v&]L`u]Y#5-b^ cNel3(_/_Q48>͛ҡe:I4:~8eZjj*bj:6BwT1Iu¬Bnɼw'ZW\$oXIPmEs~+{ F@@!: (6nO{E@vY* D69/\`1r$ͷҏ@A&,7ttv,`zz ]\_Y2}¤x:Q2^Lr+S)qXV`ӥϜaeo8 ltDTeE^ịXT0#L%Z哥H+\ur3ƼwE:3uK v!y%67 lǀ? էOؘX ZDA9%6_~enne1ӈ" " " " #pҥ[`LD@D@D@D@D T[䞚8 @D@D@D~:zb7 aD@ r+@QbD@D@D@DHwGi#" " F"(J+*S&|Ab5Y\rADj_6eeeE}YoAd4 " " " ȿqfRy[%[~-'GDE" v?,M]0*ޡ}L,@D@D@0tEjiBCC#""*$-A""`,0]1d\]v͂`o!`XP CD@D@:عp>; 3aÆyxxt lwo; "0u1tРg”ƹsgc`/vKlޤ0@D@D@ "ږ?@;~]V5X @  t7@ 2tC& &È" " 9bv!00pĈ`ۜܭWw˯J t7@tC8 *L c!" " D\%zRlllۍS8 n@!t=:&茠65*L " " fFΌ`w)..k_3Wd : 膠3&h0 " " "`N`4_XXwkw1ydU:{>>|||y<+w /D@,t@ ?~$^\\\Zv9#" " כBCC[kջ,//V.g_PT<|;wAnQD@ #7oބ3L[GD@D@6!Py\j7ߟV&6(rUUD"hU .^jAt-\Okpw6Vߺx1;bIPED0. :&h[ӂ+*+$rsEwk29#yzJĶ?ys b6[}bj?e֖>Gp|J$lEiٿ=ރ(kG'Gww}zP/OZ~[;4xa{g:U9IW]ϱ5}*­Cʫ3*@7V$,Awl~8I>=ئ!zi!:?r;''OG^㢢۽ Pˤ¥[xN}bWS_]%ޒݓBH{]u@¬`OegO;burxz;wpD@-c#^5 T",88ڥVW?xPHmaQCPPP+2X}f$C8ԵE7ي_sJ9N/SIu nrӆPm ~ jBT;a:;7'15R͏M% gMO?״p)SI 15sAY\|'};#_븗@S]ƆW߻-YJo_ ; ֶ^@{jue -t_i۲=aW gI!"Mwn/8G,vx-?mjBCB@5 ^ZZzZ^C#9;$ ؎]fugЃI)Pݝ4黾I>uUnl]pO"7K,#D"@+{ v.0]J (//^^^>xzLoE^ڛ?>t?nG}$;۷0ֆV{$.sNhSug.r?GJwpqwhU=PF΀1՝pɇ5vKO?W ggz֮WUf2]t W2=s[AzS/:"{/{מ<xkluri:&v![SAnMP{z8ؐ຦MMpŅMӰT%ė $7?ïQ縺r3ڥ+g܇pwwwuuD"gT ZYU1uLLdB$(esNm}cI<3'Oq~ѝ3VvͣAT*nnn$8.F0A`KXXX؊wfiM(ԼDJgͯZ #^a}<[t3#A$*++o/.0*BDv K0zjMM X+d2W\\ybFoy܈/WD}Q9}~= |}?Q7;X%;c#˲m|Wг^Cٯ@VOKИ .\qvv,# PlhS]tqӒ2l(8F)vf}mE7xd &.h2iǀ8^Nꕪ]K]^zqڀ/XD*d!ZvKGvڼt+Φڥd0?ZH"?W(RwX@=zJ*83=qK &Fg%>|_^F {AXMM6|Ȟu=2;Z8Ny?}eRtk>:/{^=0chbk(_͝P=Cy0 TKT{\{,dVc,/AõʗK?~ EQשՎ|~TJ-kI>[߸+YLd9er;z{ˉ!JϵgOQUȔ<PHBQ[d]UN1c=yR٠/ QfW-~ݖvZdJ2'f`ū52]w{{{v:O***2+g=&6G]ɍ;%t|{hVnPt,W/^.TnaT+ g^.,VE:Vneg5)02&X<Ƥ!Z-M;so~6wXsU31.c,GEs !29QVU(|Q}5++ʨݯk]߲r}{="WҿUdU4=]2yA$ :zT la5:k~T JRLviT4BAGY]RPPQC*GfVA::*o.iAwp}&p.*59%)/RgeH\!#KIL~ݑGTW+lh$D 2}hZrS߹QR`" *ӏfUZƺbS;xG h3~-ܡ'>#׶@h\0XBXCn "\@8.ݷzlUKrvUMz흤xz[OuS^4P1,Vcv rVAd+to;45l.(FA&I `!l?jCY.i$#TSM?<xj`jk(_ jBo;0Ӡ ˗&YxIjFs`:/Aâ[CV<ZcH׼@ akji_4'#m#^NȾU @m9e=8l;wVse+?.OUTA"XK5$F41D/!&$F&%+hQQQRܙ۝ݻqtԙμy;3o@S9_ lι8|~ìƭ8Em7z^S<$ ~wQN\al_]48ArkXjvLI__^7/8cX.Ğ}QWߺ~RN *eG~/w4cAR x٪x44+:ꍥk84YV\ _83 #W )u W@Cj:C j/r/ufBVVV6y56fQĚKc7?e{tb&FS:ҙrCȨQ##<Y>**67 w%!eD25ԍAu,eK wv EԢ]^pfHj <*omjV2~ìY-;|V=j`/ K+[5Hھ.y72ޯXUb6eD]urք)@چ\uCQx cN=]q}%__8Uߡy>=rVgW^EP*3AFaK0Ԩa/dkǀ=ΌRzʶqZ X_^!<nOiyxtq,+>//,_m~jF5<22܋͈ݹr<EAV >Gq#'=rYr~n9UN[jFO8vRN\#}bVdKеWGH\K^a'33IЎvFD16Ԇ![.]r=ztxU1&\6T @PFp5nE'7rGT⼀<soU da 8Hħ4ԧrd1M$zެy[bwL}gϦ}_k=fi]b5'F!;6.A%bj.!cm̤8GM0qs C$LX`2=Ǒ.xs Eql#qаm۱'6OzerpP/,Œ71g.?]T)*E/I&3k>:ָ5=%ފk+H;Q W~mt*ݘ?~0{ 9~0u/i9bLGPO[@PHq)5fWuN3rlN|}+Q. κd=5'.3Z E0|cl ~lŒ0wF>󽱽]t XWr|Ӑյׇ?ki9b"eά`a{oPAnEJ9cMbi$%wD^IQd}~q|L-yewW")L{} a ib +VTkoz҃hlbM3U:/a Yds}-Hw:#Zk>%[C^I}?Z,^vN\C~zO:_+Υ?cQuiBuTʌdb}}j%0Y <2i=RD$5_dkr~Zw@ݦ5[0aEQuCu%8QcBi+Sl 31b|i(<|"_} uYW#4oQlhd,%v;l ɯK|rX  2֫d*qk_I'`a>JHٍBr5 7QrQQu}S0' nj1J@Ko]H6HɬW+XOJq:zܽ{9tڭ۷a?UۭCXC6;D]"a%F擆mϣI.}?Rw;FCr}gIG{)y]-0DJi80@)3s^7I"3Tbӗy6 *|HuF5IeJ1C ;>sD}sl\*BAxw:m R]ڻt l QPI@@vx4?_l vl8Oʹ,>CO~`$wD>%(d(^J ]aoo)P|R騶{Iot-k?sk/k]hmt/L y]L,N^PZBE N$5_ ИQȊBz č;S˫L/tݼӚ l堪vh<(lM[6d6ljЙ7wg{|Sr&eK;P0)c^I%R!f*[8X2F,g_oPv9 \=+fU!X_$M:oԵwW'_*դY |Օwfz! WD#ZUik Fٛ/_h H -|8B?`CW'6N>$?0#hd>\,a؂S9BnZ垶GU1$?PԯVvgbEhuԎZ-gT ;X^Tdldj$̷  cYO؛/ *Oa)"KFELvؚc'fv>|;LƎ%WÍm gAփ0%ɧ<`;D8AXejujBXt2\aWKw|LjYUrQf w*{H^`e^ÚR%tyU/zr1m @ȉcI=cE:¥i9k8UkigS @[ZmvUv7773iJJJ RrHEJɉ/ұcJJJ`t ȭ`7Dybm-JcWF.I[~4r'YԷkȹn&/9ETT -/k' #̀m Mqw3r]Sƫ? ݻί*LtQIʷ\R _|?5&zz*=Rٓ*k>ri[]נ2s"7^d'%cdj6T+f߽ ?&vUb6}`zf ֝8,HwvL8EA3P+hmYu1h]:f3nZ61t] 9Gݎ w (v3%TR +, XOOw-ju+#9//0y(D]~2P[.v#4\E>1b.߭7Jb=0|u?:k&v+M%gUS $zbx6odl){yK؎ cj@:?oHz <ޞxiҕiEqZ bΝ<"\%)ГGoxm+*F"*}x#w_,Uw {qi^$A9Aֽt:3S8>}S l ~ng̘Qݜ\{&OΞ^o|:@S|KJg̶c!'Ej5D adiDfYHXDR[' ؒZ\eiGCDn\dehd[S-Ɓ|BFb 55ѓz?RS#H@ (sX7'e\.e@W?Xh0tc?)֝Z{iҷOSVQlͳ,DiaSҬDw dj]A x*++b1}uL$Uoل),9g.Zw "ܺ5ǰkW0_6.Q`gPuXB1Q[H ӚApgDNEaYvvFLL@*38I dfA> ʮl8Vx:ݩ׋p>?l۹)IGjASWfyMţKlKhz !ݸY8oޜE/ l(1A"QaCL7>z_Z_u<|`lO8EQX0c 1ukEKtiwŪ֣]،L?DaşmiP[T'L[^xе,xd)'];{YwD A7q]3pZ NL.)H ]=܃7ܞ |-TlݼEDD 6lC :th``9qJ,Wߨ])Go*46],{N@dr|!{wa햸k:VH2F+2Z:`f0MH|6K*=v{!(gFVh܆לɽAyGŅZJgްRW -MUfOo~ ?v}r1q=ױQd.AaFSqԌ>E}[ᒷPP[18jksJv`Rqm\+]gNguKo̥,ܼzfMbU?L5ݿy%dYL >uf?2W]\qpl$u!//7++w-),U_uL>/q5hFuOoyˢ@k.4vX:>sԼGkA>%988c5P{LZ(#* =~е_;0DyknJ/ zP{#-e@IDATr+zzQa⪞̉wsnC$Z+||…[ :B肏\,!uhI=sP$-nl24@훷,J&MBKwQ/gބ{Ƨ'D:(@$쳦Ryd3VYӂwDN3r[!rG~I݂nݩۨs7?j_LY%cF};+y1U-ԝ(ݸHgեhP$ym~w7uȧƨ}X zW4?z :{cjACަs7 xrهoqCeSZCpՀ{®p8/ `1ꏃǷKMu@8M !VʏcCn=ۋt{aKX&>X@+hc}4}@ axKsuygfJHM:KNb߿}ooh4ѩ<23EҀ<F7߄:v],s]B& ,m?oT :gBZ8Ggd6YO]8>TP7 S*dKLi3䛏یYr! [õ 5%U{// c ;ylO%A @!:atMB]v._Rc XYc3()}\ _x3斸8_3DRdnccM VIóSAì*die$#SK[n*"*/+/BCKGgnj M'B">odjÐQ6TR+<ӓ#}ڋ)r%>3@bbbFF`E}|#iqNu"=7Zd1s:| '>NDYw?m$H Hd["Pc>R,*'C% 5ؖ[Ģ+)jӡ;jVhZu)rÊq8)IsWf-p\%8x vIVkӘPugZPDV.O.xfB>A- h;:INcbؠLN{}zy=nnnVV 4Du=-!`}&BTS kHHScjLO{y'G \~[F(FD`& h=z&a&M =:HA Y0FVƵ~V\(gcnn=)V5YKD#w`@;HC|P3ZNVօnݜySk#IKp棞]cr!A @  ~!Asfme򼕷À& nxAaD8F @ A n' o߾-H-y+o#Lr$Kbb"hKiv +h5L A @ ,O ,ڙG,v\mV޶‘%!  N& pfI7 A @ A~__G'fߖlll3|>ЎeҖV^mX8A ЪްRmm-TZVVV\\,p]vVɰ&+aI I* Fv~;IENȼ'u"Z~9/7@u[y; D AyC;8al# eR^A @ @gC&=}*++N*?Mږ󼕷A@ 4J6maKLZjDA @ : 0]g#Ǚӧ8wm.AB"l') A @ A @ vG`jo^!A @ A a9EAO2&.?bA @ A @ Vxl=ɪ)St,Abbb}iA @ =()A @ 4 ;w=өK&$A @ A @ 9do9C A @ A 94A @ A @ rh {!A @ A @ 4 J42̬Ii1A @ A @ A@o=PxA @ A @ MBNɚA @ A @ P")), +vY";YPXIi~uꪉqLoյŹ5KXTNݞ$++OVUbA @ m/bޙ]u(.>WXV%w+pJG:{**Ԕ/9ꋅ9-;UDZ{viwl[҃PmgA(I_0B nZ45)>]jIfmkRp +*xl3u6Jt$k{yϡ:ЇnnZ.KKT\8uI. (2~'؟}Ws2E?*Al*R/ۮW!|9m3ZX\W+xG =Uk\DhRCvQJj{EB߁v8iؾܢg[!r9/nïO8Ő9U^)]Ȍˮ~|f%{A @ 4^I<)'@ ĺ AZO-XcN84AQTB ׄD,a~f0PNR}5*d緽NEP੻bM,䟕 _҄B"Ūt"6^kRX`.vA! ȥd)uJ|ޏvۀdͯVL klia L&[M уrߌ$aa@av][R\w^m y|P Zh90|M ,ʝ]b]Lʄ)~P$xesj,5'>5gؘ?EjG`ϱLp( {V$L2 ԵmҶ[M@|A @ЊHk)9.Yl^:Ĵ֬@Ao972j6&'T@WGGur4&Թ}>8sc._K-f1y|J)M:3B,yhM ڒW=\"S 2m@GvD"°onDQ޽B:WVL TLq3[9UiV?'h6alKa5cpLN:L *K{z;56YW?%;ivzwlK̠폙w H S^ܜRDɁ%韜Ӌ]2viU72HiMj,쾚ī>ym.Wၬs<0=A/MMW77_Rv~ :w yaw!1s|37j@V"J[Gg1 }l@sݼڷI;^$q ny74y;Ѫ]nԿAHp@A,|v$?.`KGR"aA~oNJ <\{$M?NhE`)a 85_4Ċ6:ҳ^9sS-0} ѝ(K^JX9y(=F!;yLҋ**g^Vp.q= 4o)A  &vO#p14'_bu8hȋY3+`?z0.=/d ȣ粷f؎9ȀziSYϟȪ{{^>[VlM1&ʚ[Ds[>_=$ՈoC<ܶ`ZD 84 K)7rs9P2HaTuk 9{^*4nHρ]3 Ifsso H EOANj%{$K=_y]hoM raf\JY_8US5p[课}('=u~m<!ΙcQ7ؘCAiy"غog.dƯ~rE_޲KNi/y*E)|맓J9"NSl`wCM1v%=tꡇg]kl.Fډi/v3XO)FAT&.|{Ay1׾vYn>{q~: s".OƏtPv۷NY̙:g &"_NRQҞ Xuf¥ hG}Fi, 'tApieC_rRnC_@E R(Ӷ]qx7?زLFślq{.c nč|6sSq򃶲 {OdT~sL%Tn?hRKF߯=_P+jsa[f*iTmbsSy)(|F733?\s>G.'L;he*?nebivRZ|R'@.I=OH 4cs?r=v7=$)r5 y\: IR/&oҪJһ텴IlTvo:~ ?en#T~\-8nL!1tHWFIͳWo01?P˄\ $z׆ߒ$(J?,,/,BG~>~TuȟLg:ӦSl$r+*3Ofˎ%gq&:^U*v-L[y $S~v@_9|gz6Qsoߘx7$oDEI&v~8d!:5M=T,r2Y1I &-KJ|Y^lWBBt&37;JC_As9t ϕ9I C.q!L6(va\"PAc\*rJO{EҽS~ĂUǨkq7͵pڭ{|^_nB O &ƆcG9­fa:.A)Fy)"g/LM3pV֋ed&Opy!~y\ذ^ټ+"0AXJ $Ǿ.9lZ;qЯtKcYR耈Qm} /Z@οz/{`ռݟAk?[c< }|ZECI󣱂 ;JͻZJaeVd|NwfL?|Zw.YX:"O5O<-!_Maf< "3CN&ʪ@֒젱xrȖwFx<1YSpeޒaMvƳz`zKtۿw{luA[2ꠦf5B޿rħp ^@@2>_ jr~4<ࢇ+XҔa?X/Hi-AÚW B2m6gO`R'o<1{YW-ʨ䩱*u?]ʂ Jm1K2mI'b2iʸIM;n:vc"32,7Щ]6gW&snݛP*vt+]5wfOkg-gm=5oew/V2Ud}6W|2yOEV Vd6ne˖;gJצj t:9/%S6J&HLp6|QjB@ :'l79ke E4K9gu~^R^{W*p>>+7d-V#Gg=lHXj;Hͪ$n߬ @+ vdzF~m8YH:P-Xl34!2|%NkBG r,E^%g 4z=[:Җ O__fCbHJ.lgXi: 6[ȅ#;C'M q-yɡ"{;n.Q|=bϔC{ QA5--Q޽945OJ V ʿ` X9),i:uS7*|o?Eg<#&ljX_CK7QyNMz ]? 0' p}W,oe;B nh;o2b̻I #Gʹf7 iT~_OH?7< _vS=heH& z]()]<|G!mypåH }w w _ojf_q"O-U&Ɵ9g9}hO¼#]lזM?Hj3gA+ ϼۤHzmHnG˺LOM9V&>ۛ}?*O1: 3u(\\7ʆ ?/AAu[͋؆% !n ?6嫋EL>O7hۼ۩gbw:_!5(~Ⱥ1CW(=3Q,TV{%괖*l|uoA3+e(!s=<5Ti=Vt3Yq]GJV򢒠-L䯊V@x\ $GlNG2].(^!iAo-DsI"ЈuӼ^kFtO,ԋeO 6Ix ?0+19 zeܟ [3E_ۏx´dc)ELޕz[ ˇ6޶ZQ/jDEe͉EE$ehh#g?t@ԓ%b"9' ){,3uTw ^bhÒ!J#VXX6224h>bCwFW9)W y7t?{^wG!L)C”+6IY\~F.}] #{`D)v%b/EwN|1/c}_ 7ͻFYtwiX<*GVG"Z7agBBQ# J%Fv+HPW#eU dvJtZ؍kJr1r2uu6qlUZOj>y׆g>a7Q&-JWZЊȮ_;)Fjt0V0"zL8,3Um1nvV\Л\FģQN9Qm8#R[n%C[(fOKދ}ךrRY^ŨcZLwg׍ XsZA_yfmX.(Mnn-TGlsв9xڠGj%ӝC |$*iR/rJ PON4P7=Hk)dU<40/- 3#rUqӐ %°&+t \:%]dKɰx'PC1aQ_ŸT97CƥWfg) /n6yicϚ;2б{7.\8E9w%X° {C砝Q:PÅ_\e m.vtS,#D'ɃMПM"׃->K@7lߴnGˎ!CmALjGSH ׯ+a?B; (濱8h+U"[SN q]}N *g7fEG?'} Nqnib g3o0-ME)7Qz>՝ƀMS3p}x5kȾ:)j^JR*tRT[R}Eޛ μtJSנM)*`y<ġ y08I5 Tp\`|P;Ӱ5bcQfJ,s:ʫ29*lH5ETɓsl݄Yh3xeL=ZDgg1ur;etįR]s!%{Kzz1@='_sFM)vSJt,|tQsIH}ѽ )û*M{qDwЊ מ-U1{p ]4zkݕF28 rJgi6J@Z ;]ūzjxO<G O{i?+OBS b願d3']:܅y$e7د\I)5Ŷ]0}kXkG`K>'W:ܝ̌TQL &(LyџviIt.W"%_hb8ZH|:Պq`G Pn?#)j*]w D%qLbj@]sUUnS_)ݹ^*bpq;=W=! v3eP3L,[~ѫIYWRv[tIU")؀K?,<؋Y!Έ[Orsӹ%#;Sꁸ|[j "* 9kc%(4tk2H0fЊctnEiPRZKV S=>W}$P:,5L’, :muޫtd܎: =L3Y|W|_3tk;yog9|YרLVJ<K=v,نƜ hN"9b㭝hhQ#`JTs@:)ljYJ 6~!v6Rr;Zha7t.Eki:V;[7ة,@EKUGf©H_Ȓ_-|7Trh392 hTG6¯O= 42ΑzqRbWK)kȼzN V773(:M&WH~W\ (o{cV.)U&#f[n>:~H? \`eՐJk8bL=)ַֿmuU]CYG2R8 Y_=Z<_EX寿=:ԝӍ moU32ޖ6UGIG߽FqEso<_P=cq 3W "(8XAe^z+̍'~I9HRO#h*RSgmj&}TEbT]l5 OQ88*:Xyl{t1]QXC`mQzn!lcW\P,圑i3eRvaܥ}X$_)Q .ɉF:|.'  |%-k(i*կQtH :9JN.k 1GA۰tOfz-N;9cdO,NyJ=iִ1G={0ѳcd䬚 rQ}Sah ^َǛʴNKJF}o(Tit__o*.V޽^@lG_'N_>PH~K..,DNԔ E4EL !Ŵt;gA}gqj򹵃ZI=|> K Jc@1O.tkk FJv= (u2Kdȍ^'z {{vRVWNaxxoYޕ湫/Y dH48 T5z\:c;73WI>Lu9qUJoD6pLÑo׉Ց?oBHƦߊWHy[Ui8Z~qΝ2`~~btkknxe<6~=tϤĖφoEd<܏b;&&L+2v4PJIķ~ˀl5p?;uNZH"/wSK!OZ\ѧ(7*a'#9LVb⬆._v\Xu=@6Rļ4ȣ<ҬP4-4@3d(*r 7˲?,2}xg9y+ ol0px>+aj:!|20E"2v K]ZKKщx<%)JQL'Uwz KKg*2VVI T!S 1ˀs|2>p{ Bſ!&B&{.xI5Q\z>=xe%~J%(mF3\r(+G_`o[s|%Cc>OK*xEۙP+SIKxdR] >ZLfAB>;S+KJuҀQ^غִ.#3:l >]]dgvH@ygI6\(zZ(,U|| <"v[D Tdr.Efb,;yqzGꌹV5B[R]YH~4~+J?kvb95.GM'R/$27CmN&j~/2>?\D)eRqj/~yTHV1$zs[F\k://Z)4Q#d"Ei{x!M'7KITpǨz)ՠ(6ؑ fҪi{ G\R'xU~/n v*_N {u˵<q xiLIubƜ0DDa6ZixPU]ZR D5 ZTyPԸs.l͹LݼK7 tY7HJ\D-to0כA¦~'i+Zb6}QJ̕>vm"1֒@[~HM [Vv F1D-]94BAQkPv&|RʂTm)ʦ/_]E|`^VZe cb.\n&i]Nuz[GH+ED®#髿N >$$nkq:(6OI]g :ڪ[Ծlߡw8++{ ;L"k j7$pn9 uFD*m=PW=\z!1ޢU| '">Pdhxwn?c,JhŽ:⬖x)|ҡ^?UaVQưAءc8T|u_nw6. d咊?pxAtw"pW?2%}Ʊ>fgk]7=]5&Iqc*z $2IYV){_'inܞ*!#%Q$EI]Rbر%a>ս/6FOh߿9cjܮ?񙽟QL.S6rCU?wcO|} u2/\EDK1ySV Է$\a@WEcRyBS:FBW?kƧYP\hW~Աn:"$1)Sdږ(Kp#7Y{"Qn̑Y!#7S |6ZFX.a~~EBKE;=t"4:Pn6.P@dt R/N.oyաBз8R>@jMGYh9ijܴ|.;x і2[-㼾b2/$%3^h"6]\>0HR-aՎHjm+I-3fh,./Dy͝1nC5C5Q8{mJo뒋0vȺOb2:+ZlE;p L\;d $_6:V}6x7r`~y^3M"@G'U%S՗62sZ]SiX`C~V&-y)Zcl@'=*kw*LiImXܗA̷KZѺo<+.AuYnW`3d""Opm4kpz 9k>v\mɀn_W%Sv(3ߋ<&CkM4F5(9Co2I.B1),w*$$#?T Wƽ}JX!\D, #6 S!,ތP*vevUٍ|䮼t#]wHPbPXMRQbeˋpjJ\a2IgFU8#߯?\$&&*ݪ( *.z2JAn>ȌmLU*'C#OqP)'e*cc.쩱)DiX+.y21 ZhhioeFΉ]Bs{LQ;(+ RKkңR{66•c K”3A{hOcabaMITk"wnRHX;jmmFXLo vFf !q꽑ˣQ1t*V %2Ǩ=+J+HC3Gvm^{EEpU6hbYwRaMPZ'kdq|#C΋J@= Hn~3XztRP`dmni.TVV:| @44Wwxh}UqYVJБFieYP*,Mm}MeP,i88S}]ӂb9تF hx 3vR 7@R[tT4ؚZPN]mpCiQEe#Hjad(V7q@M#ۦKWUH%z<κg:u 1nр>s6i&]7F1&*l^]>5$+7/ 3_cSpvwxoFF:Ók,pLGo 5s (ȶ^24vԊ{.|+d༭$n]ӕkeuñ ?mъzݎ 5>2v@)ޘ]EUIN".0eՌK ۥЍB :$;{)m.cdӜ&VXAcӪ{ܢMB]#ӞFZiPbeJŔgN OMh)l}615typj֯ !gRL-X)ȏXC<}O!]zd6 Z k3 Xxꧻu2vKB< Q,,I7TGǂ{nzC]-TXucWJ8]2E >50F=k5I-aV/̀=yɁKdaq]螢/* G6.K wC$؟nkd b4"~x ԭK;^ _22NaVoec:ӗwT=C4LXƧ?=L҂htLa&5k5H\_5W+a{De߰UoMaHI{]HT䝫á?|+-F@LOn+M .~5I+gv5X\×;]JMDN9J3KʊbnbUHr#\[#FKxGPBa?hdupȆ8X@TRˏw:TdlC2>X` x$9u47jj*uww"]zfxx t%yjZ T[)>rL=Z_]*#2yeՅ2aC;m%XLUZ|9 ڹ?$z,C;㶠ܹ-2k`daL^faF#`^TtL]ߋj>#t!ݎ`Jx[\s]nj҅Ԕx7q /X CʌwRof=M_i6s Cf8بkVğ"fp e'g$IVp]Hib'J#Ga3STQqF-S+}2Y9u]u>ZNh*GDf;7&F[\O 3>|-ma0F#`B=;;UcA1x݅Dj*, [壥 ἏbO f[̃O'eh TVSЅl$xr4*bƑ:rfl]fJ^lZ vA#is;D u~j`uAZٰ] ؽ"UL:|9oUݘj$ac b`0F#`GEfh; ]3iSxD1y~+パ]PO!(xzB2C5) xMK߳acof|qB ڄ@&iQh`(GTYnU6%4]c d`62<3T|g׍r[$ijZ8gW^ے(j˭Kr!q[dK`0F#`]!@ʞO뤍]g.|ᠡrs! x !'I:1=IZrd P`*J)^0}~/phz]a)qt-o=r֤?%Ⱦe￿a4g]Txpˎ+ypf_*:'`0F#K0iXýkw^ͽ/@a| \08ŅRoͯ1y)qlhı2eԀLز(?3{ ɞǙpaZuʾY*LuS2*NNkWG*~/)RGXC=s f)Զ;rbѫXN wu;}4WYoQ%-Zcty}wVffg{@NdOS+2svd+zr%頋Ā3-a|HfyVjқfFRo%%F~S=,+SdTU~VRD^{Q IǞ#@p `j0᭤F FSWWLD@[O4fudi Cu /oӗً:Cd”{o?V*5픩1ڝqƟ<ptA$9#4c8ٿOv1<6}pTvWjn`0F#x^[6]8BX)d޳'E2 c3~8ݾ5|?PkWfϧ &׭mX+1o!?hA 39KƮLjr"WK*%~ S 咢M'庯ݎ3D-#'ߢ!Ւ[}`irؽr,~F 29׵ S92 ӑGi+o-TӢR>Z ‰q:@0Ir~|+eR Ec&qwKGэm31$|wkkY(j(KHY"vcT8 F/,p}ǯLXD/S̈́1Jbҧ9.ZYYʢ2PVON0i 6s:Dʖw´$?>@r <+}[:aEzFz"zoQ?eU%_%!W`k??XVՖkMnIrM߁O\z!'3k5HXYA8M&Ysgl\Y2, % K(vxV%?׋vv7'{f^{|U2/=z&-N96wDi{Q#?׼Hrt`oQ]ewIsr]٦;CYͷbRD]>Y6]r}{syHr P=gk\Q)NScpu]qgs|_@Jk}n"3n5pYYf $8! 31Os!5T[[WN:w*~ڹ:1{&GMq&i~?)o:y &v?'sO,:Nx?o9Pቅ)rp#`0F#P`u{8HAJЄqͦ_:rg+wqIj_9QuKAcDzf' @/8 $*""¶T9#&4<`~vD ,Go(ʝ,pJ{u4rG'm- 0LkAT DW~Z+َ$$SdՅZT T%yٽ2254gnBǞ0ՍYF 3%Tdh6$ݡҪ$Ajii~%Œ=C]B,gGHBurQhkyj1Qc(gbPuWb)FIu%)Z&H(`]"ʹ%$PJ\"r=eL{Z}ENfmDkլS IQidr?7&JOV]Ij+**U"I"54-hƝ&jmܹbѣHD `0F#t-TA>Xo N18 z%F0qs߳KDuTo.*ue4渄DƇh,i0-֣˗͜pt\s,dϳz 6F0**G,4 G8˛;Ⱦy/G^ƍf^Lo+xT{,@VmC=mD%P4WQ-IQôEk4,kSS,i٨Y ba+WيHCa )1WLLn+ Q2Gżvi}^+vj)gPIf6 Y?O%W:QnET`lN_pMÏgw%Dk=@˱iSnk/LbS3dq#`!!l`0$hbP"E6҄L@%4j1w_r 9T1t_VD9+RT161F?"J&FQG'7GN [݉APBVHSØoCCfmdNɱ%QBuU]} 23Tig%RKsM CEJk[e}Iuy=ZTnJ &e5V Ts9^)*h&Ut'}tլq&˪ ;iI_ pDS׿}NA-CV:{IE_IA*.V%\W *p#`0F#Ѝ ݨ]man秃T p<;[K_W3#F330dp,0XgI`->"$,\M{O{EX$MU qIK<[%,aDӃMs9r*xVoSAюf%=EdƊR%%I}i9&N4H;fJ0-QD#S"zCgZ)%z.ap>NJաSB6FJ@nLZ,w=j*z7\F#`0n@'JHAeނ!W Zn=^JVyxy4yԴ}y*HjWfܸT ?VLGLfёY}}ˁI7&~zhKG_9㭡99Ɉ1G%=(Dl#\ ;bH,c>t%: M3Y2'J..nrYEYsRgܺˊIQ~`08Me$\v2 SpU dCf+U 7iZy!BD:V5i6Qe? TavI|%um4؁nc[Xt^[Y'AO\:2LK>zpJN[2(x$Ȱ}wH`y^,,#XV'hghլg(42"Қwt9(}4w1pKcG/P0xQh<-Fg`M˨/ܸcF#`0@3߱cG3/s6AIkJ+jZ/[S2S&&?O!w966]DV~-&'zfAJ3j*}KVqGǶBc[q\+*+(Ml]xH}Hn^_ n=L%ֱyI*W"82kd>yZǏ K${ДÈ3s/g;I3{"B"9{>9F^tdΊ$ZIBZ).iQ^q;ꌄ+dA\Tf E!O7U K-FUG2QQi+G(HӔ\ZAA䒼9sg&0ρ1&Oњ_de $L|dUJo"O˕I+O~„iϖŖAȹFDJK@SDbH ֖!7EQCʔͱWA%ydWR?}rudڜvЙĻYU^=VLJp%O}2->~~iP>9 I^uP g&4` t`6]j t@5(H>ZPJz)F#`0@+WvK8SWL`x&| 5%?l,\hy(c_a&Պpa'2jp]_bc߃Q&:+c)2qoxdeߛK%N~qN; Y(߃l*) *m>MZjjuQKp= TGLl].34 5WB釺n-`;܊j.q3yVkz$?P$[l(?HYŔ4_tsO}2)gD{(3]#=CFLEm R/ҵNmhD^JyFM%8?@[f=%JVL|ɲK.d ý(DIviZhh]^jW: =;BqɳXuXf(>䁪V30F#`0_?~k @bbbޭʼ(aMPZ'kdq|#Chl(-^ ,## SNK#**yquFm+m4sܬ{c@ec7u`@XgʤJNjy&rA&.ܧikn@߇z#.;nO[&+VA#6QߠsB&?-HĀ5of؜ IRkX^$IAtm-5+sX,x^:w t;07%-%2N7ry4w~R+++ۨI){{Z[f6>ϢWX'F#IkImc5/1NÖz]u #)vө}h$گ]1y 9MQ#˥B<Ѧ !`0!b؊U;WVE&qnFB mΎ2*8xC{g3tpɰLpث^zEY q'0'CuBG-KIVj$ȉ?tvKB<'FwøF#8[J+L,i>{ZۻCcISfdhgoU^`0gS?#`Qgk!9A9`0s$ 9e7gUoK~{[TRYTR6m4DI2MM0PO0cĠ/elTsp>F#! m`0F#`9zģߏ>U矓=Iiҹw bcY5Д݃(F眝i @#U /| ]C}<8(gSwJʡemҋʐnx+rK>}ư;ѓY} }wW(|;1ZlѓWD+zi%\m8LB!0L0uDYT1s[F#0K筦t@uN}Upj"T&tYr ɦOZ?IfFg?-d;;빂оufX/7 @}EGlHi bS^2ti)CG0lu>b0ˋ?١ƘSfTF#`)\6RWe_5HY ݲu$ᢷm ">|-\&hF)IMC s" h@?2a01_CfjOLzNrΡ[i+k̑jo'4fބf2 p9TD˻AƒKCgV4dRDaD9Ї a}.ةQM>VZk`?P0QF N, U^lQIArWVq02.Ȑ+uNRwi{X5q.ҜɕF8c\O9wJzX/Rie^}U>NFSn 5Eew&6kYK yVA;] :^,{J$N p#ħWċ(&p(1/:%zf Pޘ=YK@ ܔSb8 ؘJ0J\-]_oexCGU49=һWn,GTrFa[MB=$EtQ$I+O_J3vMysn;D&r\'xhx`{1F#;ˆ`goh z0ŧ)#ɣk[`X{!.!#3-אuGCX7SA:\**@|ķn~HVruLNP0² 9F. c)Ip$׾dz`{YQ&;z͛AHoaԦyMg˲؍yWa/b.ǭla d Q7ٺ!민 h9! Gͫ=2IY_'lޔ[Y4;bmH6:I_ÅD+g>P};LjLO8)6]ԝV,lE>XVGSd9}P';VJ rfJ[Sk kYy5G c7N-O%q? r]MOv W?ZS7#`a koR,0THټ0U}k]圾i;YzQGr;gk CPӶ50F#!N |TsWW4?$//-#;J%XP|*U\vz zAAygPԛY@O8LݢaVZdM ШfC}P2 ND^ eYl7_&Ŵ\cZLty3Ѵ)fIcOm&\M\|;H(ҤOgQg\EDy\}qRtOͨ:>4 J7ob}=?xQC3Jk֕&?&d@&'_jB*F#`:}%XaS?Ͻl#:,%oDAjONт3IOUj{g[6OUi~Q?lq 5Q0 k1==u-4梆{2>#a%q(5[dc7yd/k8 >/5J |('BdU_;uX #`0/-z&d"*==JzYe6G_Hݹe@&ٖ wLȄÁ7#m:᧍[`0/5=@œ$#j"hb" o:[!/k6"MLAqc4;œjh0̓f,[O ~MN=-?*1woѭCdmUc}^^J/V26JI&OSRE(܋hr!ņh-$rcǵ6W}t_U6=% )+XXIL:*{6ζl,uFEQ_c]Z{5y[tl Y:ahDtJ/(!~r>bTHfo lsKL8`0F#*\Q{_K2Df- 3wmN̫u׏) E.H=JIJ+!zP"HhXTc>GϏu(dEUX&&Ц#>t?tX\#xQba럞1B:c.ZV̒H#bHvϼPB6RHid9Yu]k :)+شͼ"-?$@{h2e#svZٓ- LjpϨ81{wZ1yOTĄβ[:4/؏Q5 i1/*uKb\.rE[RDZ@:Iyr.0G)JZls9|7כ\LզG[f8jrݢEk%'D220◂ܩwMlB>77n:5ei@SѻecõcR3*[om'-H}~1K\.sKdSDwQ@n6*Xz>lQ\sg7~hNK':7o#!(@|]t{*v 6Pz R, 3%񆅾,}BTcQL bD:m{)*R#hk8״|*S>aoMO_FSccaqHV XD\g*Pw"Rw(Xښgt{;jc!nuJIem}9i`J!Hn;XiF|Aa, NTVrn?VL&l5A%D l)=mo14XrڹdPQt}#8~Ĥ,#LISG[OxD]TєhZIygt &уmMrX|-c&?tPub~w~dXı1 ?{('\\LU[w QlwZ_c o&B[O1p>LE_c#F#НA f~*kN9W2a֞^B)$f^ ) +D26 .5u0'杭߾^\XuY|T0sFU-4O?/.i,# zFמ5,̈Q~c)0a0  ߬-,56rj C䆪jiF#X 22F;9zOF ~J` tO*7l+\\$JxA!q \$ФQ*Z8r:1XIl !b#ZHq&g6ⵇBZYEdYV~~s@6et[ I@əůnE}jV X3Wd\ YPO|^Zr % $K}5$B>Zm[;Ny2j(IuX~ܟRϵbQF-dDIږ)$pr#[TbSQ#=dH@/Jfut#9 !VSØTۚ*gXv̊=ԃgԑY1F##5;͛54!h[UԪXX U@e`cߌKUFDdJ!ˋ@Y^EsT<&}b [p>ȳ۰YJ *43׺d*;s?gzӦ"ly,۠asn]-)l]E?OwNNi.eG.@kL>:2ȴTk[vC!%ҿG-]:#K"#QY^#7}f\9{N.PW .#~, o\EdSLAE1hwhƦ:1 *H]B͙]hE?ۈnmG_Wq甙JeSywfFgg y);JC6.y w>)i 3?JA57&ډ"`0F#t`{<1UL{fOA߀ jH, ?I12M/&Cl|Eٸ)d!Iv,F '1G*>^˛y(s{;6TYǚ2@R|ahjKl_wIqYũ-'>m\ԭkI-8$c IӫRWB8/Y(j%y 619`{F:`b3z^arƇv&-Wt>!y3z@Q4fBt~lF^,C(]0!99iyXF L7dlI8d~\oB>2yx;6ufn;&IMM1F#`0@#0m()LV\yL\ #&d*(2WC:`>==;ّf$ ^>4^Ixg\dPeAQȱ Lf5׷ *}6y;8h$]_3|&3BJK Ro#`0F#t ֬IM2mC4j[̨Ϳi3.5|CP@CWj &5 zvb ~A߄oos%P[9³L{mʠ~4KAVq!Yk.bSs=^4]yOx\օBy!6d~g +%CVzqe g={}BZB޾;rNӭ h0GP3’sȫ#;M6Wҏ.M],G"'k\PQV}v;:Vϫ?_4ҵTeKйqJQs_'@D |F#`0F@dff~gz$*#֒ kp.敶4~MzO!CzS<m6r&.\(5EpQXXζv21_::~X~&vfv4R#37W6npt} x4޾Ҫ~  ayqoYE9{l{ $ [ln&ѷr#mn:Z" [7Zc0F@? aLL~ #`Q|']0.j!kD (ڦgyGٵMO,ke ٘V>ݥӧOi{܋|GҔˤv Lܼ.񼼐MXAĺP',~? v_1F#`0G+zs#F#qf|€0Yr UBpqK{DF#`0 <,y#"u_y:"+.gVbc0F#`0݆uF#ГxdOfyc0F#`0@`i&8!F#`0F#`0V0=#`0F#`0@ L#f`0F#`0FE+gK`0F#`0F[ n3`0F#`0F#"O{v=.9F#`0F߉M``Ը!.Zۥa0MI|`0F#<HK7n=z}`ܛk=ױlL_wK0c6vUui'!F `0F#`ze1|ܺ5S/s2jjFyJ_%W\U؇`zSs`0F#`0m"v?.!5KS[&.?/͜S93e6HF#Ѓ`SYc0F#`0! Ny"\g'%Kl qrw΢N(p F#y`0F#)5oc#:rii1\[Keחތ'I-hP[[li._z90uuJؑ5`Y;H;i:K'/eGy42"EqVF#`0@/#޿w.b@o $zyۛJ |f~ wc L:ܻsUa0B'N:Cڦ%g]~D7y3]iYtC7EE:xF`idLFwKzg F#`0F#^.|9I_7cظ(hJ5l'K`ֿA?]X9/r3x9v'|z8)2;jPRWfȝ:1* y=l p,H9Q#t'^zZڝ1/F#`0F#$ Phu!5.l~TMzH\M_G4=s9.]ޥbxO\w9_mJHEШ;=ОRSگ Rv7DeW (8Z\ɛU&Ż0! F#ڽB3`0F#`0=@ xf@=Ḿ> '<.A $V#>'Oaٷ7ekk3!Ic?|wdבJ%M!D$|ed62/%);F#ҝ`y>MNɃgrF\-#`0FYBHzA2]lCɠKl?p3"H(}POf/#-E݈diGvKcgD Iםo0ƽwPF`@6=S^ZWtimya՚GSstK%ӟ jϜWE ZR)6G+EH>Cl=kxrx`ȡHؼlhB+wk-pK+M[̸P  01# op@#^g9VκP D\Av.Y@: ~硾wIպ. ߁Dk?F`¦߼iuePS&H_ڻЎHOzt?#-))Gҁ2pm96@xEǭ麹zؠ-]陟&YƩ ڮgГL>1"# +/v+3`:Pn%Y+Kl)d\P K WѵvZ>xjseypXBbXC (V}'?.gIɫkJj̍^?ZS4oWf¨)cNc~zsfB:g}3m0SG,,Y$C1/Vy%.ɞ׉6r8 7O?ךc 9Rp}Izp&30,*]n-){l߹jTf}fjM cf y@O RNX~O^tb0^E@g7$ yyy555baccc///3:ŽOkx3Ƥ6GQ_sa]ͪSœ{D>j33=%bbow 8VV, :E͗#%zYΕge>Y>Nz=z.Vst.EA$+a V qK:ٶO z(ۚlRUm=^N5!%vvP[W3'PR%Ș+,Ay5y9镳E-w%3X3'#;_ZIA$)GW:>hP?K9[kEpݜ@p;6i;h=rGAiMC&+?7]C&?t9We~Ozu Ίև9`0ݏvq+v)!!@"=P$"""zF$xߟ%q䘉$5`KF'~{|H <V 6 X.1PףakKDvΜME٘|ґ)p(fȰ/L#d#6(B92I F͝z(NdT٣'ftPXcƆg'0J̯}êZ>Nύ pG'$xMnƑdOgv#x\Y)>9>o?L!)9p,D6 .abQ|ЭJ645YN_10?+L-,m/h-X`S7؞\dQ3SI;sy+Sˏ^.iL@)=8W&3l\ߚhӚHwϾu-vp/D͵"& ěk]8.QwMMM2g/L^\ )K٬J6q2e+lQTx|MY\Lɯr|n; 6ڍl!q]*JVwI?3%905yr,ʻv=S)90 H)w-;¥*Z0=_{$64:Ed%UaJ_sG"Ch:} f oSKRJ\+ k&*̡mh%NO2%},2|ӹLµBߚ!_XSgᦷ?8XVH.c0dЀ%36+IJ>Uo`MԒye}hWsE؄ñ2 ]K=X*8}]W3설˥ V(u) xI7{_~aݗȯ Esf z4Jn/64hs4XQ(頧!9{ӡS"W3j93+/Ȅ/ACBe;{38_; 3p+H>=ubYÝ*:iG l|*ntBoM.W_g V b\5L @y`痑TzeP]w~u(~/BeյBqk2pRnQ\~zsb@([):mmvKH'V'4dh,y4~eG{**ځ# @V*8TYzP^F~콤Bh*[8\kN1&~4o&"}@j2TmxbF]FݚLU*3wg|#'&ؕ cH E0F//*i@ nEJq* RSԼrMDbGjeJ ϺqfL=F 'ݸ swFvk|^:EJPm-HH$=&W%d Ds~bUv+Tb}"J*+14{n&)ɐX*\RI?#GHPs rPea=ݦDC nJ󏸈s}^a,:I )$P 󽩧 $\>{Znߝc:&t.s̠BYq-??qDHf  k ?JhN4*x>Ifh?ذsZ87§$eir,8)8\!gү%&M[{U7^;u  &N~I-U*NBՏY~lsq;iU8z<^K(uAۺMZf7h㜫y{CCC= $$ˊ{њ=(;qJׯmŸ6JE5Zd&M&ẑ˜l,#3P%GӂdmAZLf;}igK 9Bh~4P1[h~b/[;ՆOw{Gj*!:ҷݢc&XKs'B$UEء)}v¼1^3Նd1Dh'd¬^'@M_nBDۊY\^cf“`â&LSPsX+П%ʉbOf ,Ka3BeUWNZ,Ci͙9[uU yL\}k'mFL1gu r਴y.M?t~YΝOq;6Ɩz鉒!YI&_~aIk1ڥfA&hv[6ݵSݜRJjCLJ@Sr!.zLB } ]rvxgsڒŕ-Um-Ih/U@%! sRh%M4sCR?+Tz|”UvUI\<%ˣ.>|g|g57E]2uϹ!.{w< >-pq)w\7~AY#l˯aV?Ed7507V54Ű7^ﴯQ o,\ f<.cZWWKŊ~KǗV7qe`eK1^Bz)+M#/h`|IIuLC QvGx7Uaй%km C|yg\z-.Gee5eA)(T0){$ %@IDATvi%F_N[IHo(.]ZR碂AgL˾ThN|bZcz.g=79}YvVÃ(aUh:b/!A4"v6)[iKpT!c-gm zBj:Cgh7Ⱅ߈gw+<́(6Zr`d8vJ؛P6tBdOe OvֳwLC7|_Vgbل~r_\0{vmIB/B%~dL2;/TpG%RQ#z^&4suTSUD@YԷۛ}~!OM757 cF|C(a)eG-ȹ_.%ӎx@gj~ gKU_98q`WϏmq. ЖjM)N#4 blY}"t&fD;*@qRHU)IX_T[tNxPhQ/W,㕛MG3HCNt$E5Etj(=9q'}Q;!^d&4rP?n[ K͏agrs @j3>5r ]:}^=:/Z&Yȑ{:zBMΟJ>P ?7yc7@*,GpQsIsW(\wf"Sqv*7¾a۰Eq8Q #`0D@}I&O}W"PP?^C(txSpK ѭ`]pSQqt9eD.03'\G+Iڿ h^!Bz¨1\&k}nL~UWz厄ޘ(n$sMVkM%az4 r7P <4ݭ P9a@M)UV\ڧ"P<)ݥ lDfR= #EJ"#4(hFUF;iFkk%uEh f%ʎ7{z=ŵ"foU m&!jj+.`/} O?-d*i-q|ؑ:t`J~M& J/-3xGT ̃5zbfwzVw]H\Bíu2m']Lc~&"]{\4|fA~djڼd ZpwA3< pFḞQ"A,^v'%ūwFl\ ڣj@Z~f˼Ċti_5?\7@VDrԖ ZL+9 7NCL>v$e^$J{fmD.͔ D{0DiV. Zq},kaҮ5=nG8j7 @SeqZSj3ՍKjﮨDc$%3#ܓؔ]O2 xۆXbj%~W$u-&afTHi}TFV t vߝwlKuڣR:9)$ʂ88-?8PDRX\6}#m5J# ~d>9q`,KO?POߛ1ӝ$"'Rs0DEfcYfbrj߫9F(k!?ܺ[r{ d:ia.;UTIT2F#荀Z7WT$*( g': Ƣ9"@_5. P `n\}T-Of H{ûXY*.ʔV5)z`~Ơʒ`-QlFi.P ]]^M3pD{kJZ J0+-JGI'3r'wewT-+f`@BͫQPk@?yR:#[7S AbLߡM 4K oD66]+rxy9T:YOE*Dz_VCpWrג -0<"j ^\MjXAv4N /w+'|4 j5B:tԴ rBQEcV\f~e'Z3`ٍ G kW*N<ل.Q;DWy\jMP 74DjѝQ+wI[x [cY6yV[ [8.Dȗf>}b6XBPSuz1d;мLQkkʫ{ i;[OME~>:R\XV,(K'r%>_6q"P:lPJkT WNDZ{9 Ty g44CY[_A'+C'S:س O'. ШHZ D&vVD|m9;;zqߓTE>rLĕh쫚!Fk ;mo`v o'oJd-@(%dI\A\z%+Q.5< VÇ`Mnp)>,3`N 0}*/ou@Ȼ}Py`}$AY"%*PBN;-|Tr̚DE~n7'd# Qo yDi*OUAEk]zʿ<`kCk>s]mIDc,::dCN ӓLŹ,Fu㥶=lHs C7n&7cɖLYj:Zef a`Y0;"?`$ &c;w^/R/xCio"[8MKDis'sQFITV-Mk IRo,H i* iiD+!vN ]CApp]`n:K*>70_+BF4Vbs6X I&.WeׯGc<>@\^d~<#rxD7wSƕos:i^"Jxwf*;"D<\ dC%2y1kx:dh!5ξ3/;O'Ӑn~~5:/RwDߨF:p7b:7WJJb\*obsз J}v!V'¶.iiTnR1u NbAQSi{-${" +߹Y3dF~IӔ_[[?=LQXњ#FRӀ7f_tobl8cΌ~ ~J2OI8jȃ,iw3~d6\K/d"2Z-QgзpeE,W?r҉*\Rųvc*nQ౯pU;ޟM_Σ7wlhqH+}jgy=9M9މ#9_Fe=OSUDEh^k,xl:|_^:{ RqPdGNW#:^D?tHc4"r/@܉J|=A)l$ RSƾj7qt:|`0} >ؽp#GT.zS*$#2ynjyÖr]/eJk ~*#01Ov~2Mb#~Fstqٯ\Z¹y? 8R_y6ET8e£-QRF9R&eeKЗ|VBSvYS< AʳfNKZ@|4Va-{yGDш>ݐ<1R$gɟCr37õc>Y!ń]ַhdƭtːqxۥb(,1_P pC! ]px{A܏ѕ2c.8EY:W e׆/*(+f/S>GDhvgqlxfM \o g[=ywJJIjvF!IG]X54J) j>LK<'Dt\py L5Ji`8aI̷jO wFlzxU=`Ә}K/DmC"F()ǁv uoAאY >pPAn:4VydLjQѷWSz؅_L-MF;(EY>Su#|BcayL{ "I/zT/,nږ0>*|ЙKzѼ3@Q~Y$XfjWsg-lhFP("zp6 g8A蝵F¼_1c$_6âNO*߾-4"x*rVКLZ@[Ah Ty6Ϊ/"R%`pWf;#n"ܢ]黧;khlcZl$~XA~L}*^_<:ZDe|Ͳ  lx&R5p'`hX2056(Gc唵 NQ[KcHbaY\w"e99j\_L}r=$?/1 * ʅR܈c`*d2D 7J y.:F(ޤb~=M)SPT#=V48\[KSNoO ]VYo{L<"'~yzkWnf;>x_W6]{R~}XZ͢2!cl`fg1l6*+pd2P;0^+Y_dp;cc 󶩵p@J  gaIUUX3{; P>lz$~:KDOdX^Q*KbۭeFQFy^ۍ55êf֪QqR)rбѭjpLڟgǨY_ $vc;u!U>..cbb!؃`0|?9'$}P=!Qc~\ֶi :OL,|4ཁk+R:7!j݉72c%ݗEqQ8X8kJx&VI;eb榗vr.z.jອˤv Lܼ7KLDcCmvڮXXt@GCE#I%%f7%U9eЌB %ZXvSSIۚI\5b=L`)*mjagڮt;'mS,fJCN OYIjzCg}1 utx\#ٴ2N]TD0!+5#>mnٽh h`ev8#`ytZ<@㹟^.SvgA3'CđO,X;ynV(bher{< AoHh%?%v^]TKsc&Ō^@?F@'7lfn~{tjct#)ݸPF#`0=V0u _qއe&]f^$TO5'ϑni*'dCg4KW7*ss=3M=\U׏9 ۏNl$t4wY)Q͝F7S4`0F7N͵lEt=np> Ė>YId4tqtWu1s1fCMhs6Mj2r8#%+4ڹcŷ8@LicC0F#8X)Y_ݩ83IsA^v<^g~bF#`0nC_:XbFF#`0F#`0w(|gĸF#`0FV^:G̴.8E'=gEf])L/601~^ug]f`0#`0F#`zƔGl Z{oqbAaƒvL冦[[aMT+M[I1#̵Aܺdž,U3` p;j!0 7+QokC&-4RxkeA>&A056Wʅ~&v,[&n7@)2б&nl:~ \cT!CL<Vy4RkljcF=!`0@0v05`dIgLu &N;GRٮ˳:mBN0# :_}S^z=䄉^W7G뛺$K12Yt #F@x3Ҳn67U;?Y}ы; N1v̪>:ȴ>!G9(1% 0 Sl:qAe`ҙq#~b㻹{S#x=y]oEڿ^O خDud6.Vᑗ^vӿO6uOl>^Q؞^>#Mi'AF,F#`zKc+BdlɲsDaǢ?NL%)pa1BS0yLAbf=13x e9@TЂqzC8хCoJc.8%3Fd]ߖzȡ *Ձ5x9(c6+E`+ȐO^tO)%#C# u&y (Uh>2NVlo|u|.ɞ$ ;|黗{K4rE*_avV&/} ;>`JhRP^ыˡXyxשFr:ys,!O;KTpS0px?=(?Tʚ&WCS2fgo2o_ܑ yɷ2.˾_DbxF#@8ZpAMAj9`ٺD-'$QZ8iՍeldfkoj1fV'yX֠0dz+Et@R~Endh)fb9MA]#04VKE!Zrb˫|,il~qcX&{_X+&v6)/_X%"vTMupUB,.+$-rO)Q-:n;ŌjC`,`G@FHfO%ل?"לWG8Zaa;`҈)wՒ館Ea^P =w?{U>.r_DDDiIiJv|,+*oojfYQfy+,2*"ܕYu9ݳ7X9>93<=g93sNeHzXA|q=f]s;s+H]BK~y5Ki3 4d^.#̴/8Љ/xMWц7uz!  [3 ?|@U-0i{6Em6AzHޝi[O?Y1T=)+j{z%)#*"O"ׇ刏]_RiN}BNY)6p0sA飆 c^i1UdZ9bjD~%9g+6J*L肔ro)6tҖq;%q̜@F6kuYruxoa 13X1h^rr&bZ7Z!')?룟WsM c &\ǑwO ^}#Ӡ1,p׎dI7 ,Gq3p*R8WE56G2ݣ'Cُ=tUҫȸusw 32N6dK_dIWs`j"7w_V#ln|tDg}U0s7`+p Ѹ2XLZ?N}3\޷R5N8e *=]A'x0cWl\FF!ۡ3`/O+q΃n1oMiQ հT:C>\{ӄ,Wމ=ˮ%6cժ5T7.kN&u@M)@$ȞUvwӆw>l01v c&h:KWXxrN`Dw=5@$6kbixu)aԿs_#foc{0O'2!T7iR⭱0td3ƎPst9sk 2:IRsteЩ k6R=Ӗ\zƐEu\.ޱ` "#9b{e`\-7Z.u8ק!P%) LnI,;@}Ԝ2qsSՏƩ_,$3fNx&o_$<);^u-]XpsvěIvY Y$ޑ&G?V1W/ϑ'3q}fcQƈ;jKJ"k?.Y طiĎ2*LO9xTW%{*_R"ڛ`f~z-79vбcIL?j&@ cό9e1C]P)Ij\C58yzCn+zfW7ǟnxyYdφOj>} }+,tJ4hgY[r*Hݹ]޽=ߌKa1dcuyF5 *ǁuvk97ʫpȼ´D nX@}q_]^f ,TQ'?N!>6$2R`U. YH,E4I^/l c9LYo#pwfx8s#[>~C8@Baņ}u2\3ը+$cknV?Yu_$+eH?iQUg*7B%iw?f뀵IS_̸}標X?/%ơgQGJ3|S<ʿ33;RWtR·oé/PܕF|#f{7Vs~}_osJQgK Dc?/k_>P@Ts%l>߽pv~\Gxfw\_2Arl'_?C˛Ey#KL2#0lĉa&;}a{W`fMmrKbEl!_Jy#8zH2>薔 \d$億lO5fW;y7r)&_jZE4 OKX0'!TWx.i!R'R! :@ߧThxf\Ic>E]6 YrP❿8%a'ѱ`&_Kl ȠO &6\F$!<Wcp FԏVk`Z{T*CBl, im*QEm ~1߉5+¨8djNf޹az'!;nDO5BʖS* )![b>.;I$1mWFDDy-e#)B[K)G'bwbX#$HUF|,Us6?ȁV.Q@x>!T%#G#8(-ёržG6 :[27ϳ]V^j._GG*sϑ ,d4kuKʊdͨFJzbu`uv`v~ pwP+z44g0gLV+!aNdpܼ<]>R.yO &_OP?&ZDpT_Oߵ][;VCgPHCcsv1]9ġZM.}iOX#AvaSd'v9.U7W3+xڂvz|_Y{Xi Y{_ʕFI —J(px]fa |o SI6JIׂ<;R ^TDU=8 `yN}]s}~qm7\J.#mm݃;imv|ks+-2RNoŇ<.41HHA->@Xzg"޴=^8q(޳ o@EFeMDX#T;+')ʛ%5ᢒCĦͫcZwtGxbU(/v7%D?fTLɁτX*"Ç45wru9 U SE# )=\( 0f4m|,]"\^Bℒ+`F9U].< U bq2d%<]yY~DsK2[<uyj[:ܚ+4[d$dJ?B߈f0@<,C>p󿿺08 #mW* Gd6 ?G~'P7Gbf'DeSVc0: pJFD /I[<ʛzmro$|jmZ/Zw-.'o/\''3 x]wZ ms9]\8;>79:F[YLJy9Hvv'^< Ù5v<ǾyI㲼C"HǑxsqlkߩo˃/ݾW~6<~{JU+P&[X3kn,- D{jz YdnAZ1,לkb烖|/b.y)iMVv> 1ɓ{-4z8eAyBE}$GhM#/A{iީoRb'J]Ҷ0\+cp#2^=@<^LESl.\yȡVn)жGH.|xD{ova?_Fܞ~|:a:YM!jHOs{[0H}OIVl} F@tL6Fu!wo+P>=]Bшmm1N+5.b}yYdFܪyĨ"[MSi5anԤ0G.Wݑ̢KtNS֐/+n.Y9uK3KCh(SSXd#FWQKaF:+'z(@xczSVW.=PLyq&fIҢj-.kNyϷ0 $*-S"/IYAȸhmJȗ\[<`@1JѷG-F~L* ݠ&șpcy0Ym"Of;q`d7ػπ>H"9'ػӿ^uphHK@tWwzrǏkap81zƉPqV^ܻ;gߜ3+m@ښ2=eK̷nrqMciIY)k=߆M<ㅖhqQ}lx"6cT0Ng m|bMW6'D_H nHL>l |&ySEuy#l;^r 3HٌBE=t$M"9w f?E!( /v~%ΐ(3炕< [u2cŰY٤҅(yk s"`#vU5h؄\ə;HΪ̈́hвiˇ13qdE _!/d 1KH^W0ɽx ҿudʻM,MNdynaB^5ߪ!w;c`lH)X|+q%J3r U1ˮGl%2n[NQMI׾$wqzc)qfnm:D²YbZ`EB|jj5k%GBF禤?Mp|~4@xRk@TƋOSY[d1>sQCQ]} ĽNҏ% &8F=i]^ƌbL9~/7}Y<Յ`[Viw9oiϼEN.*vlC"QQOSIMdgYoBp@7"`sҍl{c {SF "s6vxTR!ʸf&jXfE/j"'hy.IE7irV k~K K+ KK%O%5Su |sI[#W4k*>0)%!q΢]{cw}ϹHe0Í! dO4'yZR!-y/#9:>Q8?gTK|!Zt!76H-j껳͍ J:RJdDW^;*'*ק{dCn| %;NM]̴_Ӵ#r$ѧmE\ rphϙԷ,% $)&XxIt}#&{bUֳ?7{坬g!b߹E;u}TQtBޣgʈΞط_F ~ӌ8O>v6Z*mqlHC;>Ywq3:ܴcWZt S@P&<fw|P< sަV}ލĻ>zSO(_j>YQ͂lq| &l=6jAvLX#-[#8yɑ*g=Ϗc3jV~oى>?ȼo' XJ^ B{ VQ@ hd;[y(RGy H[;6>o|֞fE/m{ڶG""ܰ1^(ҀZ:T5#6KCbn&B7pܮ3=5:@ItزnuSo1־OwG8d}#&zqB h;N+|HsJG-E[d,?$_Ltwk]dy/Q >NI3'Ii{ܿcWg9֐w]ǔHe[Jho-7*NH)HɜE9l|e/gNj;jI {I¯N%]T.z՟>!W?v81nؼzQ^*{(`m?/7y4rN:NeoǮ'Ya CV<[&y,0| 8zӺO6#d[ ݕoϙo5T˘ヘ6Ͽ!TeuZ{JsG';=MuːIQ!JL&X_5DHHvF1yǶ ƄΖ[RƖ֪^q`v,ִ _򒤲q8|jfŨ]$LPUI`# Π.̝FNwAcO,2ҩ>fwQ!Xh^dM헮!T;#v v0n,~5BdT09 >KYja#g$=@c-hu^w}us4aD?Vo5fϵ u;eEQP3y?c8V1Tyl6,榑ħbU8}3DuUt3 kl嘲Nk׉KNRF5X}(tWS!,}O:wFHckrc<'~Z`?Zbgk5,:DD`Qcbۻ16M]&Q5_bVX+KMQ Ruщojl!nK{$Gh?c򉃈5"@N.B{w=%eٍ?Q_d^+'\79Ch).=4yvֽ֮ha%)=4` $?͛7?6E)6%۔)rI}DqO57U S+K+G;qR&oWG7kqudrK\k3YC~^LXǮ9bbT(jdr<ɫAεuul+C||NeyO:DZ+iSl]rrFyS RӫNs^0iAĻmJZYrvZSTW)+wɧ:f*@tw ӧwwCϾ;?$CE[[b{z=T@<.P=VVVSSco{.N+C8;iHD hER~;.!4u..a9<#t*9F/hK+k~{pnn5JϱqqyIW:򴓔w El^ՃKGwM .cr_ A{SǾlŤ>`uJ@S{hro6m3$ۤV]u>}`|Gp,wP1 @ AD3 @ @S=q`6@ @  "]heJx> ړd@ KL @ݪ.jBµ@ @t%Ε ȇ[8+d5uHVvc^M+%/;E?M}[ʲyC^=ZQzTiƝfx}#.13Ǯ"3xIhy܌Qs3bk ׂmʩs4\<1m{_ :iOaA2XQf>Oj {^"${Ի7Ug&K =5S!j"?FFЙZU:؟ |E!d0u_Mtfejy()<Ф@nyB6jw3r쮸r #jq6ӓuTf:bm}Q $hEu2%ȁ[_=f1D󮩾z$n%Ց[\n @ z8W*FV$55M6e@+:E)&5kDZуv9ٰ#㙻ґ܉aK+^[.#-֌ʇ#JQU7eg_}9БҊPdNYwST^# d(*իKCw]).Dݰ #Kx:#ʆ|r s^V_RrJ%ΠK…~/O秅)'O['l J27uIp%]<-Hvw Z:1ʻd܏bq-\mw4ʦ:!~ [M͹":7<ҟJ7yu2x9"Q =ƻ+Ni#\(|<;.e£qxUs”(1m;W !SH E`'dP./m^rk29BSS}3B: WYdyz)I\}fi5\fKV3O}:kg,t:*=>k&vV‹Y7ԝ\;$MzQݙO8 ۇ^(A(Ұx2wuT^VLW @ h1+=)*{肌|vd`/&. wauXvP R%ېXLv)] =%Y:a,HթkNڌ3f]6#ܗ^'>F̻4IuH|RdWl],x)OwqI<-{IZrR7ڲ; xvx|-T{c(eqUkk<`!y1=2+^w#/AUj5Xx:eV% \{" 57>1|ږUy2i[P[W: v'oy!ĕ,Sw/ӞkB]@W]"RNqq'*S% ZHȴ4kMԬ(O*\LzpO+TA^jkb{w܈ߑ7A`JGK4 /Qg^(#VQjʒ/p7~}U퍭*C pL}Wߜ;&u`s< h;GU4GlOVFU-oQmN"u TՐĸ#GS1e.'BEJuaYAYxlB" I|Vz:}A¨~ JIKC\4[-q38cGlG"bXJ]a< L1q|je[7,Ǩ#}bXZl4*kPH(fh#&R{aCbt"1 .1} /\ݜTS cѹ/V3ݣcv5fC]={n.'>sk X ͺ @ <UiQj&S zQFi# ю!*V i[TٱB\2`qr)/h&=RA=4(-$ӄ^J ԎR}Ȥ2X. 2\^Y{12X31 D}hrO ͡E&% !&1QYL .m<w yao v}]'>RT~&=k~y@p#0&(SnKBY»lDnkF.qDYi$1FokBA_ܤо٩9Ny=BsR@J@^km->YX)rp8(0qg=4?k20hϧ2.5 #Nw8QKiTsc3AMcJci+GƆ2^={veUL6ĕ[6е`Tjlf!mN.z*J@ @%А}aO 3sE|9EQS~O()-6mNb@A@k|E0ӬTI]i{yoe?<n>"ōKƌ]Y爹Zo0Y eUnuĄkpDKߌcywWFUY۰r5EHʷCfYͧAu(o $jTdkF Ukntϟ; D~ػ=wEy6q+o٠_mQ+5övd$0^>N\_YS`B\|AE Yj$\Kf53vŤ'%rܼvQBRޛ# KUĞ?-XHYkzOkKpCKՄgSJO'ٔQC:ٰVRD,5O'>))8aShJVH@ =VZ,\-n|ΫAbyN:ӝjֶ-5& yE{6J~;ioR\3IҳW榯YMGOkYz'"SﯦKAFyؕ1<۱ -v>Q=Q}Jzp xհJ#-dԼh× AfSD:: /W'&1 Yο߯PˏUu&iű5BˠɅFg?>-&9>EO# g*IVSl|{vŝЈwve4OΊkhH˲1лܝYq/6W{Sݙ+w-2MSjr#ը($p16Nv2Hw akSNoaZ~b4Axx?㋳r0r*#@nGҁp0 9Bhw;]@!8T_qBa6QS&RCK}>7sPsa'媏!{3rF^WpWq,\ 7<%XV zghx␭SՔw"7`Cuk.Wm272/O@Vkk=d±Aawȹv,/hb/w lU'r02\K4q zQ.?%%KIy,Q>:5R[YHy2v5PySb"#BN+3ωv>iŘ?_4WgoJœoσED)iįSY5yLC&Wr7ʰ1Ű3 _zjMkb7M9:_2o.@ +˖tsٝmf (h,{:/G1Y}R >у_KV]ZNlrta,(O+]-bͨݯ DK*#`5xQeaV덍qU5>JʽᲲ8Ipt 1$EvEi)=Qz-~cں_pM_gNUYMNϤ쐞B~]q'ޯtis9t^(\Rr rck9;sJ0*C"RTc\qzd __z_5A6 =@sE{MŎeYg+[[:zi}Q6V55r\9 <:ܵZ%~P [jKJx4V߸^B-퇇 qzT7!GoeqL|ʦl\0- {ceVڕUL&^)HJ&3Kg#T^.ש$X@ǂ`*R&9 F.-sW%`|HQfׄC}4tN)PEj2Wܿg#:svS y=jPGuT05WOIo$S T2aV5RG1!37kZm#Z[Y|\ޮ922~僳qךpUSxTqW}Ȑy̔WF!b-fYȠ]qNz?3@VZ3;+"'iOc1Gq*l!a:G8m>P^L "={* 5"Q[ `jFWr TkJ7E ׌l>5ԟb@ n&~͆ݣRW&geoOaOSUMm3|͗1˟d \ej6,߳f*n7=O=b['xFE:sr.q|/I4W}BLb ͮmXF}2o5Z1oa 1;qռ'iJp4~[Vl @<:UX*.L x<.XЮόDK4ϡӉǴLVPky{x-fD CgZ4-=2Ps-N,j5@O2-?P7H!=V̙OLě (8P䛿IMù޺5J [uR[xhSt4l‹%g. 86D,@ǛVacHO]/,_hI"̟obX2ҁ<aEK'pci"hJ^uz'O5g,8vx1[|ꖊ"5. qΰm @@oꑭ4i}OT\&l75q< {nװBTZfŦ8I}Vʻ()pdrjS&lDP,"|)u\ppWRk62޼#f9֮E”BpH0\;N QvFUr *b$dU޶Okwju]t/Y~^f|Mͩڈ}.A܏\t.-ys:rXӂȿG̋A5[r?J7|?nFg?9OgN\BM߹K2ix_03R"N.&>9-+d䝅[kŷ׺xdAj AF@</ UuV 7/T:j*E7 +̤kǗN1aM~F_9+ǥ[x#5~ Gy,|H3L''c],D{>An/[@ 8 R)־Owߗ1>rG֋+ktP^xY,@ 5_Mt&ѴT5p:a:溡w5= ތ#,}` yzaTfИAp0!E5G=y̤9߄CgG'Nռa=H=o1M^\ɭ < N]Wm/7L{ګY)pz4Qslotw}*N@C xS+ jgpz0!&V2s#/H\YmH=\]{0pY{]&J!P'ޫ:&vS};)(yM}o#쨎Rӭ Q&A󳪥8v\j\QVu)6"jj1@4fmS 4`LV3 % y4(?O$GO@ z %M{-cOcoxܬgO%]Igaiq "c7-KP]2R./~7{AF\qgs_x(uxPR˂./^ZrTS7ʤYߧd;鯟k 㸦ԘtgpŰ-@&xzv->*."s $E`R+sD1bP%"]_Cc̳N岪j#ACGXW(ǓMf6/%[tWɫj9KLosnQ`\B.Cj$r6Txⰶ1QYaiΝפQz<|T#)kAR N}d\DGvIFydcح=A LC $'C5}i犓nRF"L#ON.kĭN5?{31 NR2o|nqNaB.? N}6خf2){L.  _ RP  I%a!w )$HVf_7+骩.*\?Lޚcd8YČbҨ (>G5R;?}j2p4fс l`bӀ0ݖ ,zE&a@ @ @~+5rEEW?!] >$ڇ )Rܸpʉ,=ael gKUq*Z%s+Z½PKqs'x38J+Ԍ5\XVf{@ 72p@ ڈ›ah//v|IF%F^ǬЩ4F#R} \I8fͦOlB>69u!'#WDq񻱌ͳ:z{g,ιp:B!I|\R!ن3~|ۓOrDSƥ؀KF0@ @8j>jN-xmI/G$75pe˂:^9k3khs8TVk\sJ_Y:.!1CŠhejWe3ގި@IDAT%SױR?I=r3:5𶟝8K 71$J|T@?p?HmJ)@ @<pEOޓ#ܩ".^$uHT9V6NBN!,gq̭,l;S3]p 7[QH*둲DK%De'QunT3[8Tjs'gkȵh@P~@@z@ |kwk{2:`V[2ǸYsnKGkA[wrI< HPyZB@ *դPAIgbO |EJ z6p0 @ hi,!^ F0=MZRqg) )rʙz @ FqŹ;nr%[C߱B hģu>6@ @<6L,ɕ7wJPI=j@ @.#.C  @ @$GB@ @ eL.+ @ @ x+[l~j@ @ 20EPCA@ @ x4 p6mڔWhVj@ @ p\\\|APB" uRjZn=BgNsz`*@ @ u`:N-2t7ZnaChiXu7N8;c9El>6\`G8mPV6|m鶏R҄,_mWkȦҢzd^` 1]JkH.5`aMJʸbKEiu: * 5w$ djgv**+P>EݚfSK됱ͲJ ٻꐂᇀY9;Z<̕ jK+a@1&;ݕmsS?$O.T͏~>ԃmmnB!vJ+ašc_IEmrI|ZI|Fy\'(r}fVv4N8c"5yx9of׍ֲi~ͬչ?p_'ׂekAYI]ߢ5IH{{]4=c\_7&.Wж A1mA/L@5YJ]wӡ";$fn: bzd;^f(mlBZwCϺ8| B!ŵ7TtLw;z.?=߀ycBfNu|S[ZrJʛ؍P"k'M") xJ^u:]Z3&ǎR*2.GoY:FfA͂l}vs_&xO f|=<+Qs} yׇ>>ωE @I@z‚^ 5W'g"+@~ ̟?!֯}(%ɹsLLLF!tMO.mhAY_b:<~K2.H>=DOWI!n]S^z{fm9{Jˈ9]CrCrD=DNeٙLEgZ u5,60R )mlBl@ʼ=ZzkqO=%؍ڎnެ˧.?LZZZzGM!#ftȀBM .h55@cG'_^.Q'M&T}B d2=PiW(aϥ 0z;sܚ"ճvZwQE\uʭWm M1/m2_2J23Hdź\^`6=֭Q gW}=Gps8 @ 嶨.@i1sGK5Y:A\G.x)|H_{λq?}|^2S0^AN*-m4{~)ϱ$1I^SSѯO`Na.<>VJj&TXox< א݋-WN966utLP$_x;jԨnABWpTd!(+~nE/qsގ2:u_쿖)'3}0g75Oׇ}͂S̚~tmv޶LbiiԪW)B_Cwv\P_88ug?z 1е,h7~=-^.w91cҧ2,=!',Ƨoߘ{`HNv#Gfn_4By]xo]bt(G\ȡm~ Ugk2b*OR2FԃoĤ܈)޽iFcf(|-]x^c=, Kݛ+ZXΈ*SE?wңsTy d;FT޹ UFv[~UP"w`\$ ajāTryxx*30*dj_3z|5!z$s%% qhcFIa앻122|)v)(讟h]jAiƅ?3olYa8(J_kFk_-R!l+|Ƃ#]V,>J F1|WXFym)AQ(t\zEmN<#%#3Y=Nr?qҙ##"p)ڛK"D@L蟏bY! ss 젧1uI-%x&ybӄubKٲYfm7\Ig_S ß/>6M@b ' %]7t2fjK@Wj!{+H"e &~M%#'0>pOh]1@ z@YxgCɛyhI=ٺnqU-hEykiRHp/H*JcGW'xZvS-}=8He]yBO2E% V,V.\1=”t;d¼e˗ s] 9S:ǎDw2#8hFs\k'O2}$%(c0/XRiZĵ:geɹX~7Jd4pҕW]ZYGu,-󇯡uC-n|N2 T.Msxtm߃rY$ѷk9g,jP}hz\K<)ruK!m>@'OCL`'xI|H-w9 m4f -]ƌ}<5ض3$݅8z64̧.3~c~³ >PH\D%VǠL'^ ʳmƂ(u3ZUSH+y[ {wԬ7^ګ:2-?^# ^D/*z=aaaiiifffL_Ut tUsF)T,[?u\x j ꀥl1;e"q nj_ׂO!yۏ ɾ>›nk yX 'kxj9{kHDgocp))ɳfLّT\rOtvgq /&weXilRӗ`)C-G{ڄFBW˧Y0jQvXxRŲʬRo_=hZͤ㻾=/덍Y!6[~qˬdMB3qrn){ x  i]pr߃_W0 {^pEEE55hMHdh f.ĥtSG8s2il>$m_bsTsh'Bthpex8A.᧦-Â.G8_yd%p]'r'!<# U.`>Iq:-w[>Q,ph se0د8|on,]Ғ4" C{ADWGM2+΢UGzvo@bBuOysh}t8U'*Xѱܴ'[5?ռSwobAm3JbbH˧37[U)4؄Ns4xok[Ux ȼ7ut? OIpvHXDwP%/0INFkڔ)E.oK |[֓/1sv9fHWa"r01" {t!5bV֤%r[#&) Wf|o&7_ďoC5%Ғ:*Rh;7$ _ab:w!r30쒥 g[F2FP>vY\#˰Pʞ\Lw㾊Pdc+orʪSȦb3SȷG8xVAm}~]y4r[^|jnDhJ:ro\ۨ*F2"2,8,Sp]\8_<р zK{8=dT2f$qr[5օ*2՛⵾ T蝚35cXڜQmvMiD̳2uf܊ad5\'вv2IzJc_$2E% `ٓ Z&!ٙUZv?"u4zOl,卻D"?3H*^dBޙlX:Yd)r8'}Qe;^8Ry#3 +xH q#uC.L~vH/f .\PHX hhX=mj o^#̑%= bof^#jm݄!tc3? + ߗX}z%s$ԕUu_̒EY; )&^hOoǶ$RuSV1J^+@S_v !Lni T5Yt}K2&.}#hxcߐE_%2eҵ.%$4ToKm5_HRЩW FFB(ǁC(5"@Ea8/{c*(d41>#gJ,ʼn3z 86@H$b>@SNrpzBVåӷ!'wKqv3)7W' "͛Vc~e))<^C},hyLLu`dcJkc.k2 #o~ ?H^‹| ϫZmXh/Ԏ*Օu]H]I@*B^ ZVʅ.͜DcvZz̰ $V{l4R:Qb<m(  I>btlkȠ 6@iT k͝56VN[qjj_ʹ4?}r IΟ5?l/@RCr$dmQ d,r?g0-h̖I6Ĺ1t~ O+7+3Em0IN졤K/!͹q, #O'xHjrzļ,,5[R6[ }ګ?>ns>|{i8놑*S T|ԥ YT[S@ٷ^ߺM!ޤ\:^޹#g7ĥ_qR|!E Cg;`>*JW~zӇr&x谻h IK!pԊlA;nCr@tЇ/&StEEXh;L~g=7a/>-{,.Fu/ACKҳgbcceP;Lm̉swy>Dż 5XvUV];C(ph$4$ᖗy@]jOAIͽDRGv98YVO0|,UC9Xg;loEer:/`j=1:[NdT'9mP-ojYr3C5|'1dvM֚ά1yiMXXۅX0']lI5IZ;?&Kg4bWA\L+x0D5h,5| F@M29Ut'XMd"~PN4 8 l2uٳr19"Lu2po3e˳pmbr#nZ9[`Ѧ 8v{-,FFh߫ف sS7'L`S C8Ζ%t sh _8-BVݗ.a:]g Wa껋N>9/: s٭_8Fe֖WXb[… 4.9iӈ)uu)1Rئ=4yʹ5o1 vKD>0.q]2ŝ=w$rȳĝ%%Դ[[S8/_O~uÿ߈~2C7dyZ> MfgEYnۗ$yBUе/Q8IEEB=W`X_"vP1a-8g+ֺdݬq*\̢=H2\r2 U/!Jm;5e~K"qS(N _EЮ1tjҨ R (kf%Bo=Yq*+HfJ% k@ǵuEY8ŒGd]M2Rxۦb\ =ٕ0B Xwɸ90k)i\ȪExO8~vԤQ f2z ]fV1 ekkO*('@7KwAHb?o3Hg ~:?rC&VWLٽC gI\FSR e|zg_6:~­ Y")3ȹ8DD*{bz˒BL?5>[pMPB@IyXn4z$c VkhNv$|PXJҘf; +\]-l@&ld&WR0 zYXX,`ڣHyyk<(SOaL@M%NGG5effvp(󲲲.dC A;8+`8OBFHfH+C?Ǐs%ϻC-|jc]Z|x&uI0kï$K‹KyOJ\|:;tѡ`A";5hO9xg3g@.׫Cc%%]B݌lUWRc},,L]f,?b:ywis/Ph̘1=z43{!=TsXzT*ɏ$Iy3H'-|ߧ2]!Kea}<{}1y)ŷޖʚ!FNVQ蕿ǕK78򭟉=H1 #}|4|ueQgן5jt J_J(TH ;xCu0V. 3[S )[cAHx-=Z/Ly凨tD'ïztt3ыlkO8܃x*.MP^_Sti<5Z\ݓڢ"D~ 2J| =jo(PYlkhKKq4e[n5'*5Y%W7A^߾A%O|2Ư ?@d1ч \\Mm<^n9ZB<ޏHglXP ?iߟ먁8ƶgyB^T"JOJsBުjuE]ٵr ̽:7CpTw=Ab$τh^BH ⏾\jKYR2j.y'N<š$>+ٿ K}!Xp6JvGHrȧFI˓ǿwtZ.^],]8ˬi`Fz!ԌO * vybqb8HEU >Fј[SSX#.1qRe čO: t UA~^t(KPj>|0x_\5Vu ?#DkcӾvDR ۷mtqVBWAt XseF'eGVDi>NҨqAYWx *ήz32wi0CފS؈kj("T),>#cƠ߃xrBzӾʸ)m+1Zm~)Z}"#0e?L\zz:33(`Ӈpi4[AȰewq\C}%k-[5ZZPI?; zތ{$3X{X7S?{ ѯF`zI - "O?ΪYؿc:typ0UY3r H@.c$V2R7dp]>wBMš$mQ%w,:*>Dͼ_2c߂eƤ c U>@v^!i}i(kҬ61Qv~U1Hkj9oCA\vTt2wM+wsa[WĿbNI~ZV#CnBa+* Ɛc@wR70QpXQCڮͿ*'vlTLVIr*jGE@BeYݬyWtU'*;,Hvo_=<&Y"NܚiβnSPTU {O{J4nи;CTGZ8~<9\Mt6e܊+\De=MYTD=P0QRuvM^EG9\C²%)/_:fU^wowv;"o T6hQ6b/ܝRsU`Yl:Uе$O Ó>dg7V­uqm>gxpx1\u=q̼(H֭[GЯDc~ozbB\6}qhWI4G=7Ce2yΖOee6ZrR8?&1W:]櫖-pԬ.G2ҾV0SDsl&0oIq;R12Ey9\8jãg@Oq'9!Hv ~hRWE\bQM$g0f )IC3K _o{ǔo( ;-oQʰ[ !M6މ"sBFsoC5A 8-[- %]rY!8*9[J8X:z_N`@Udmb@IDATm3Tq<3%n8||F&]nY=[@&.ުwWSBL"]2rrڽB8.&`?jMp05\ӬJMVe QOic?=f/31f)K3e!KY,`{e9V'Mub\@UOuB)Ԇc^X꒚dt+@crmXb t;lietBP^& y+=k;|*O͚NWa^R奲_9s$l&Cdž-u'eȭ9z@W=!<~;#M&.:CDĒ T& Iɡ 7}v`~PJ hoԳ06 kJvO$,*r54͌AΌ9 7KI"J$A֩E@P+7I9\Ȝ.Aꛛlb3 aY :mujƒB!K9<NW.enCCOKM<=FX `R5j2N gz\ lҴleUՍ< HⅆBʐ5黋LxфO_nz\_r%q^ݳrҡfn)r}c4,ι?ҚR}EmZC~1ȆD׶z3vn1*^ai(kJȗ~ 3+rO'(}OZJP4c[|LAPdcxY8͎P&6-J ߷Wqµ]\E-iba[St4xVۈ1GWN-_]d-8xZ'KP0:.>IwW:i 5!GyA!bWfF?z5^Uji{ q5 o\.%o(a+锥UQEaf#\U'_K!+axp- u-<^`eA5'hii{+X)A-׃)=Zɬ/) Tgq(_{]'Nřf~jcYT"yfCEr[$YE@5l36ECٺof!Tyvlڅ_Y_ddx;ovB xP'ۗ@ۏP,ٶݯLAy'mm-Bap:^5"jq% 8g-%NfP,F(2=s+`HMW_HPπ)n%JnnfՇ{xm])ҟm?: ~/>ݠeiYd#9 Ii$[v"6S%gx!Xv1ʱҥy/9A k1MVplߧ;6Q)}{ö&&%܋ɠHR2z3_]ǭ ك!gf~+hN` 2|Vo|_- F[Y}~ 3砑-u,mt o àA .-Y.Cy0eYO Am۞R`F?=j:\JNܮV4w?z#r/-M]ҹMQNn=Tqv~23/k` E$5Bj,3V},,,/2ϛ ? 8fjF*[M"AɃ Z!r+JkA:ؠ$E0F9|mۮR"EE'L=Y͋c͂MB@s/e[f"Xy,arʦ"""" APb٦iw$Vtd!f/Kx7Өݯ+P@6mW)Ȑ%a`ezN f""""""""""""""""=MEEEEEEEEEEbDf`c``xP{/K[ti#3那MeYd``xxLuOe<ϏcIvfY- B2{^ #ۊ<#Fjʥ(@O/7/)kϬX:)GהTG9e+3RZ'AB&֔7tpldc=,,,,,/$&/dB<897\,C\1J⢖C87:o"DhuzUIƽ1\mc3ˀ͸0醬v&zvW 06߮ݱ{5"cxNp?K]Wd3g`````````h N`W 0Q)UHAՙOaՍB$s姎y@vkT/>RRjٝ !KтiPė)Q#,ƥ[JS)\S+=`o;G O, "3 .S;뇪fɬM_c\^Vk>2٫"ZwIG )X:Z1X"# 31΅7x4W?TkQX t 殩(હ=-] ;u](fʉv̤d;; C;9ͨ<͇}B @伒&eK4ȅ2榧RMtR%]A~ڝ֞|͵2.Iht-,,,,,,,,,]K'`aH&o#ivjnRi%בiK d͉S3|-1҅lmҕ/&1PCGsԧƔ"s6\tҬj_U?F*6EEEEEEEEEE@]^^5>Zpt`OC*bSF;hhEpxh k"߱"./8r:!,|ߙmUňz;pW E54-^9=2'E!KЛtϨ{{`7L3O%YYMּOb 69.gAjыYEd]=ȁN1i uٻ~tHY3M Kyk`#AkbP}xWiR3QUNobc"r Unuʪe{|nSKәNc= M(Opf5Z-$?㯳5HUŕZ ǿeiҺ1)O~@QFj+ǖ];\V$[`/QP^i>?l?D.-;i+ğL?i3!? #m Y퇷+HS4HODϒ#㣪Vm'|VL 1dhޡCϢʯI-*͔܁ A!FX02mWZm+:a?Aͦ{i@Z(%~bTZnFi*ť?Z@R;u8-I4|u[4Q?qٍ''/&x^,4V VHa````````xPx/zu#x~вO?H4\B"ݘ8- >P7/ȡMPa 1[%Ziȉʓv^ ^}t5Oy:ENff`.yw_Q6qw[GN!5š1Eː'EMU9 YQ Yv%Sׂ s!'=N3>\&o؝fwöb';<>]Th9ɻ&q/ /# W[[Xu;>GAE {J WZRtnT"L7lԤn2&PԐz0v&YcElQoE-|`1-⊓@繹IႠ`H/J7E,?)EfN_3oe>ϙzYrz|S($.>p-EP5@VW$1D &QkY9Ȱٹv5R\z˭'hĥm~@L% 8m? J o _JJ\{goJڿG?]@SOS-،4…S.0)ްD@_(KKwx4y+Q+0^YqY,Kr.&ZSIigqhh-\]kjɴڕ !nxIKiOk*'[_|Q3IdsT3 wJHxJ7l~ᕁ;\ {@ CKM=U򉭢)De kNLOۗKZ-}z~Ր!Rw|8.D޾F'|E|MK|paDP%z%6ȩؓERq56FlU =g Zj,ZrkCd“[)`;Mti;/m諨8@hs\sR!_j2GϞPuw\}[u2f M]4pM0o>1vڿǏftểHQ[B}k8@? :l"aΌ$EՕ\}l.e~^{˙ D'S?^Ė.>7boY҂ti`'Q{o/ѱ-իGrPU'=[:vʘ{æ{SSf&3C%}veCn(cq{8`]Z҇K`>ةMj`3Ƈ`$*T 8XWp3NnHs,ԛ DE fD[s ?e(/D}_"`c3?6k˅ W ߔlGuFUY )rwסƻYq|,/묩=>k_bL_9{D,-iH͔t6WQr&f4OkM&ZYjPT\GΘ@] ,lӚЫaş8oGO&{Z{oGs^YXXXXXXXXXZA%Vo^Ņ3Dյ'Aj cO\B#}Y =: `_'΅9D\4RJx6p6q9B|m__#7+Vn,Ѵ#5Qo ˘D2"2k5r4Aݍ!MN|@0;4@ QtT܀>JE;dNW_1D%"er`@VJ%XtCZzҭ]LͭCq')`4'!%M[S"PL])FG^Pn-|/~uUCM3I\\#׋'4p . m;'%Jyt =8 !6!uZ56oT.T5Cf?<ΨDUn%I7hKD?~r-.I*AuN(JbJ7؞ #B7ՠ?|JICfaOE-Q7LAzVE |7k,U5}8қ:@X(|X;8MtD#ُՈ3c<*1P0ӜFeSȦOؔuԻ0!|-%⫚6jk9l_ 4 J6q_Y"(iqt(p x ٗ #sH![u5 +%M"X!UQO7}uDDe<܃ɚȆBzvW8֯FVU*6u> PfgjmSX*b  csMӠaX<xAm3=Ǿ vJL3e|̪$'nx7]bV&?i RQ~~7%|WRy9UvWQMKgFM ydc`````````h? G1cRTVZ)Q:NFaU@|.*`RV tG16>lȴ罹QpIpѨfU=J~_%{~-Z xDywݟ1“kg T˱8>uUSsnkϞE,ժ.{C$-B9];uꮡB_g_DK3"PQ̳P\tc,u0t$:q#_CmjaS=56jߦ~3JGH<](z.ŸD/E {]֥4̠?m!>t779£2 I]4+:Aq~Dw HD]7oV ^гO^V&.gUL(b4z[83{(ì,L3sY͂!K[O9mxUt6{c&gk@sU'AED!D9|m:| z]v7,Jyl8j"aNfkfU )1XF}(p(ۮ^xf*sɞ {9j{Pa<"V\-MO50 $)H?{??G/ҋGSD,&hM5b4jXb4F(&_DDcA(" M@iG9;g5أ(̇;;;<3;3Qn>u!n[>H 2EU@ l&MuQ}6_|/l@=}`GOvY!>Z76Li$.!nŖn֪bS\;ߙᦧL3 *zQB!@t#"ȈF7֋T!@"LOZ;9]E(?/H%:#-wyw?{sUy4hTNZ~\D^~rRn&&#Yt3b RW$]%`~'ЃȨ,J%":$!)YW3S&'p)EЕ,pXJ2'$%GoDd=pc2kԱK^zdXa3 q pUp: AJlkݵGVeQ$,i#L^'ܛ?ʑC9YءAV2b{9tiwѩ^ /L2#w•^LU+GV8L* }=sF:Sae*D-%\ r:jܖe۳2f*A=48E)rP ~l}ْ׹k+Xi%{'IX=4j+,K?3/R-?9xi[-ޙG 0+o_|VJVQy9eh䷧K7WjCvPGseDՂ/{k`h-C͈@m~qҕ"%ÄB"F&FT,(/˼z:!e籑{M"IYO=v&LgM <C ط]d̮z4%< X#eL k8Fx; wY#g -M~;#eeL|PO4)¦c3o~F8gҞ@gʔ"r| HXP _>xX7V6|"& aDcYHLc y;d-xC?㰛NG5 =)RSFp~^ڤ𥳐feDۣn %\-[Tyr3t7;W1]|;uQZU(rX linFXІ%>:l[)7-P!5i؂ qͥ/2!Ҍ>}= E7iAcA-B-Ѷv R]-b3YFb@7e0ILJ}WdAF x5cPGֱ"~Km]*/ ZEb,0tt~mXN^uA 3n.>z'nObncQ&&y!̵V:⟿veEa ]ig&yĭL]}&6W}?XWEs:ǥ~RE( !@ l) {pAڭmL`mC,:AE3Nd|#9+;О5+㮭 Q0̆QH%AvvʩTI lÑR LeG/[_~Jr"G.h/~x[<3Gb4#m^#Hᯋ&'-w?s-L$z眈\ z[sm=Gy)0qbqRLiEuwHc*)^i0|L%|Nx|},9A. RYiI DEw;UOðY>q go+ =[c _Ë9*g3{yfP/$R̆Ib2C#dyT?~[gDor5 yi3rY#l/C#NNLM}] kgSN%QR$'jAg?\t2buΖqD0&"mlϭvTdyĊ_  Γ' 5#Pt%+w%$,j4Ue4WEakusI (r|(3[ɥad P ZέUzNerJBF2ya\DNz.Ѓ; !sM ד]'ϝG:6Q951QmJbO&&Wg|l t҃צ,{a|$Xy; kSXw+l:Wsjb$?x6˷ f}*ڱ[$DL>!3AS}AÝ)D_ޞI;*}y#jhrRKwҋ.&m|?rn>DamPr,fc[D7O~~ްg.rw̛G)nq^4eYP%s~rL] ;MKӷ]=cni'pM()gk.qlrV0IʧQkR8ziZL3a5`xۆ^2y`O+Qo\I,ܪ 5<3,4G_4tEbM(MnΤV&:7}ņa{o}r.noG^ Ql9u^ Fm$Oҩ\NIœvGKo=ևė]])!do$dM -З{'3;vD<C$<#ٿ !s:`$t`Ef''& sU~[2] _y j pî\Šk7*m?UA<6W|Б2O^gze ?>tO9?m_NڲIL=I<4'Ԙ,#쑨3(2:$01*JᒲKFfxNW i> Y #ްm%H=#a|gZRP~FU`gLm&+ (m^U &rt3wSԯ\'QV<֮x-F^ב2x9;=]x7ݹLL^}B'i{Ɂ$ my[b,djM̱Dy1mV4M1+ V=rwDp7{W_=e`ތbp :c< S.L撶>D.0qLl ~uBDخ1q9rŽl}?HF:TZxq'>aN?GG7\图C~94pc:bL> &"_M V~=MEGϭ pNrËJk+8m]m:nU sWެ=/ r!KF"[m\zخ-F+?m54;;`l#t᳜qïÍ*?bJL;K9ўYWhud]m[=ed.^HW޻]E>tDA}aJ7vnQigu?SNNΦMh֘ޡO2/Pѵ45`ȿkڌVQeE:70 ,L(]4ULCV3'+N-0~VSqK I Qidlo#+nyWZOQ2dV!=a^)+ַ@E׆{77WX]}/یLtR]~ {x2m [zW=mf0XϤdRKeIimlSsgALF@oE !! KufoAc٣- FGY6P,h= &Kɛ_S&kR֊޾Mc՛bpc/"5^j&ɇ鰠6\TA4*-I pKߤM=]x3eEx|POD[xwpt8#D@ZJ a*2 002ZxLNOuxA~KXzvF:H܏jdݍk忁aB1rkC%UEʀ{EK9AoY AnNiHs$o1QwCy_ mʫO~Q1gvTR}S#%#μ6dɓn5Rrj_cp(ՈA6Y=nw4Wh4^Qt%URkصhwI9*PYgV-ǣ[rODwyiƍ===i{iKt[2OR)~XF\;zUPNKL\5 l\wB',pU*l57abR0\:3adcld;m6sVNoWq`@Ώ^`v b[oSlsԱ}7 ,dB!xeSzgj,]~D-@?uWгiÞ>B:1e9&o71w)TooCi_a~f@\q4fԉ&NRoʆGK:+˹ۇc ߝZFpgZS *-Ux.Tnj7B ̣C'b=* bz^L?MUqAuno +Na^ %\Auo tRWmyW$T[z"nA /;ܿ/El'sS~B6{!" ȋǟQ v`1!@ 1ѷЁ@jOXսl-[s<U#slԷ_]0ȅ`7،3~-#}՜0y4`pbO=0u\ vxQnYe~b__>;yPZ/AJs ON+[tXY N [bQ#.]_פNg;׮k[wr-Oe5bҚ ;  ˜zfe\u5QY a-:1跚[O< mܱ 3ٮpm17j dh1+4Wp3b]D5=K|.z-0jOH9d>N Nڳ>\6T&\cȃV_dH >{Ep >OWx1`E̓C>2HE_"B!`AԼ㝕kb@v zoιK0x{[DpO:OS2\2iN-tSwr '(<<<ȧb Tͮ~v K,IpA`lW,%($DJbx\C{ʗ`P$eT1\PEG˺z~ExI {mo(bLE!=3HlGbB~ċDaTP$N/@T-qKՒ/ğ>/a`j-B@rGV5-,˺'2Q#x.(FMu9a[*=ODpH. FCv,/CۍE!B!xER@@߳u21 .zX$}SCՍ{ 2i^^_YTIYr$E@Mtf %)!ex ?+ax|.N$]iA~N)7UfrK[-\Xej=}W'? lns~I\[i5W[Z6}s"m]9 Mk:8]hj\3hak f̤va$z2bͬI[2wNPT$yՄ"?&_k'EPz|fqohȰ{N+>ˎ1/'sH(;mxU5MF$U5u*^<[L_*'v),.~1VC~ ו"+Q@IDATa-Az-!;B!@ W]222q A*)J# )ꡜ7WVgad>栔S?SJ[Y.;I_FJSM6O@&'\Y?~zxlWv"ޘh=hwTcko VIN=(| T:M(q{Nfrø R֯:?@U&+FBx׌ữc'+ƭ#TUNƵ0,IX_`S)AlW vrhs  KTLGŔ<C4UݪΎMR]F4z.KZ[D}O޼` >``aspyo[{IA!)B!@ BxvlFUhpe"X}i<[$ɰlTxhXRAj][|gAp'-\09_2%6[3Ia[HN#e:4w .>w1F98 \TBn[cILi-ǣp"aDESJNM.d?Z I*ER ;sulpJ \]HJu|dF>'=QPml:!^b#91?VS34`$< aHHCt>hj߭{. 8 N0 Y (L*]i혤:iz-IL튍 An{r 2IOީ']++Ɂ?-nCxqza;I"Wq_Ru\<|Kt_A(PBBKCN1~#1=H Ë#`?}KKHZ _~ OTO50\_k א"w1މ%,уX;!ߏ2 #[2NmӸMXWQ7?g]gjKg3ԪlA sb"LftŶ|gADO5ͼo "{S]d04:j, PC,+od UFjTVI#^ȵX] L;8*`!#*O1yxvcoS-Mc B!hc񚙓}qϛR!5vYy|⃖5,|?|j Xw[F܎_x&.oUAqhA(D%)g>sҎg'+!yT_qEΉ3)?^\B. lf wCDjܙQ>kbl\>-{}pey&NJ*\pyr; CE^)K6tJp⮍K,Lf yqS>_=31"7A/3uRPpΎX-SK0EǪO<H _4h'Oa} TdHn ,uij^BlVI<('o kr xť5I'^M frmxU]P4&e0#b;rמ:A橡0jH/CWDl7^cpv !iIg\:CKNw{C}_-&cn&O9xQFR&T~KF}%aCK<ϑd͛g?f>~T!@  b`B>Ze> aA7A:ӤMPM V c~?_LKv D ӌxTvܵ;.~.ln??SfpE6swʼq4ZVTS]mUDCWN Hv QOt/+'QQ$Jdʅx"؃ 7(KYz0;zfXSw YBK6!m$cbBY$d;ŌNKxtaQ=,k5YZiZ J(NC.yM KP"fr #Hg4) dk,/(#ILr$ȚA$5ER^͹1 WV]&kwgIvIuguA]K<[t}S7!ØQYb \Ͱ19c%dM)RX>21_6/k.hg~luҕEe~1_ Q{7%e,%2oDkS ٴo Hyk.!tA& NOCv͒v4"~JF7q`K/zO6,I.ÿX =Zғgd .R`.*k>Y1=yV&ڥYl,ivqS2ػ{hÿYRhk&sI8shT}eto/Vˑߧcn/Frk]e2,f ?kxm=7S_hփB!x0Uڅ ޓf.SsSԕ.k(27 =}c}hź 'T4@/S krY~efU" ogbAuͯ,3|J)Hc%"9s5x+.g{2b\ ^0WI$b. &'"i5VYQS]d:t[M *+K>I\Vv|Ɨ/I4S`d$cY9;/=U_%< gZV1Q(v0#ZwpaK8W.%KϭJ0k A`j7~KݖUߔV3ǫJHZO.37WWo^Aj5VzFD'/.)*hLUFPnB엙++ Y%+X U':(Ru}C=bMYg. ڄf3" *&jhnOJ$Q KnCr/{dD:$JN.gpv FH=M'x#rB!ULlڛUr7\Al eSPL` S3}'uuȀu`#uF{o-C ]kש60t-90xi4MLT;YhvR3/<+dLc!۷?b#t :~GnI,JΕ_>w'$s:LgwtL欅iq5M%@-!2NZ;0eh+Ji;41 ;aEq KUS³1e҉PYM-mdu|'%Zm"@G@W_0Cg8k>(7 *ajKf'םLm%2ZSK1q GRuZG258 1%߉T׍rB!@ -d_i|BO{ew =&x5qc%ZkU߹QU!.?s_`GpмL> s͠34|ς/0|]-ɪY~LXM1:{F-UQ>%y9j_a YXr Gf`jB֋*$:Nbi}$<&+*\ JyR=Q=}|7.،E6](ӹWK)Ļc:A&8Ipb xw'k|??$Jp"c"h7giU\;y3m%/X8ZB&irו4 EB!@ @!%=_<ѝpo2pi(!}<|R~aKmMx1+pu BIyY0, 9ӄAmL9Ǝ6cc;|B ~sWNGBH|"[v;$fId;8jXyu͓鶳@Ul"#ܵpuְ(A> b_E\pDP89c{z> ad`W;|DG&hi#9슄'P}o̢33/OXK+3>;3OJk I#B!@ N! Sҩ ze!qI웏naӝWQ@DŽɀ‡>'jл!x+Ug[-m~ȪJJcعnVJ\"[{FvSUHs\SUn6^B54Fҵ>wLV[͍20nNr si˨(eʧ]k,"Pyt51_)1C)s'B\GgBk附3;T3PRM*n$wWNAV2|1=B:o'_gd68mUEONik!01.ȢF-#((c#굨ajJjHQ*B!@ B!.:]?=U'g37vM pRVqQهT_%\%b!`s9:l])uL0d鳩+ 47&OE046i=:-LyGӷ%c pOiF<Y}rcS+̍E* r!,O?2 m7bU_m]*583)F 6PꗨT~3'[9;C _}dPA7Yweko#B|K?D.(Z<(Cj\#!dD14`D@04bDTCuٸ֪/2욂5"|GvP :p9QwKɋfy ?dgjj @?82PJ޸P87`9NaCD"8 B!@ E05KQ.9]GhrG+Vc+ yLhw |s,NA(|plV U NC$" ]ļ܆+(yhAeY+6J5;፥ lAØgJ&UiguW)6^1($b}$m4\́A< Ɋ<=HBQBv=%XXyPXLg 7)v1fSAXDriơLcgWf.d9& &9,~^x8!Irė'U SBtiM̠`'_H3N݆G~;١LI/Ceʒ$@ B!@ :oi혦ÖA ;,Kǡjg@=J'g"EjTJi-цG4'L˽M>Ai!T+Nb:[xد=Ezo2?cg1w F)niDayQ.9= 沂xAQ*)StXXaԾSXz[?/>MNl ;N.KZ|,]aU`k. e.H 4ϷXh[T9A'bqy_\2t>UTB!@ B!@駟rrr6mDCIO9+\tN?ncyP%F0O,h^3 *ίe6 f8#85Ao„|-ۈqtXM:%4y«pu;@~c[z!nWY#!W_S]~9ijdr$ɫ9MuZJP2B!@ !####)B!xظqg߃_*L6x`}mzA ,^ P6㘛/62P%2t,.`Y1/"4ytZ2}tL9Dwt  v e`KYFE6[y0K@!@ B!x cjD 4G@(5qw˿|%y0S 3tٺN/jB!@ BC`E:-߹aTuHwt"CW\^Q@ B!@tE۠DD5/'+@B!@ B СB!@ K@}߿]S01˽H%G5!@"e!B!@ .o}Օ7]9g~cEqd(@ 8}B!@ B!@sQּ3w !@ B!@ B!P@3k`0vd[: owOn~};.8LF10dR\ B"B!@ B!r =[3)\N_$S:b%i<ނb"BGy0@ B!@h sz.o{k@CxD/E,ı7L+F /3`z{ !@ B!J!`2(h>[b#u H ;)₴k#4AQB!2"WQB!@ & "1u(Hom[abJ\azK?f<[E v&B! @[+LW!O1h 7U@ B!2!;>-?{ނ_; Ӣh?,E _g{_tws)bm_(+"hæp8Z],ka9` h5!5{PSC)(! ַGPW&M -PLjPb8lBRݗiZkƜ V4kAbt;ښ-p{-]c#b jX>ˀN=-Z^,Ҳ0ӥQX\\а^&ʫC3KbC%Y1QU%aAXշQUM}kmcUDٞߍ#k56ohm<k+y="-/; {v?t|gD6k i]M3sa3{ԝqӱ j1{e!@h}C3/H$j bSiee@Xݤ=oȪKc [/j{j9Pz^Әd,.x{~/5՜m\=[/TjoK]rnz4[AvAr \fy$}Uf\;`Sdхig !G̢th< K aYֵ"myeq{qm}CgMq W|oVʜ9s~%Jvu̜9[9WVbnNPtǩ 2e5?XkILi0/4dPG[`#k?ϋ%@S(eLhꪼtsì+it>l83[#ܟhcs!nNӳy+{ gO55)E#ʼSeTWW=J;6|,}gZ\w8Ȓ(]kWdY+^vp_:XP"msz/uU5 ,g%ֻ'8&A5)θ෼t/v"rnø ׶y }&2{DSLCNGe{:<Z܅_b['yEd?^Xda$L*WVC~d7Udfl𖻑~y凋F܉~%$* ;UFTÛVei{[R"H_%쒾g#mmg~p.>4sޖK7QI*C)B!P>?SRhn`QECCJ^e;Tw+E_]< q .gt ~[*T`!.4Mb {j>P> evJy|]dl"0~a']_h8]^Gp+4i]hz-{ǫv^A)%`gVv Ra63|7X0B86F;Jh+Ϳ} ¦{PH}! ]/:w"NT2.1Ӓ/Vu%m ve@ZW߅Uԙ*XyeL?5B!@40ݵөϬ=]J/.[ŰR*DKmeÅx!8&^qd]‘kItXUNAy,O]xLoh෵VI&KK2fQ3xkc[Oo E-xZfÝdk&6x|[+ϟm}gWS%:!zt땅z(UL?idZ,j/GZgmZKopf!k~Y-$pֲBj(ڃYe@ːɈ!)"F d^jX.cI-j}t.XyU=goMJKx 6FlA&+ BD ( Y@AQp]E\<`!(A B$33g$I7W^׫nSsJe|.5tZ\.C_CBUcaY6|*`<&yR}@v,,d(UY*`t09qt]U$qL.8Ro =W..ϕ#@ڲ?ÇFjsP b#!kt.&H4E5zwNӝn-&N[5է!݇+WU8^+&y龡}W[TPDaVҢ滸-'; n4|H<Մp1.ݗnT]q8P eujHg/D i[jWfs,S Ѿp&n*}km { :E[I殘o~")BꪽQK_ :yVmgy-4uuJ=h|k@G&ESI&qL&?^IM<3 ՞;yG .s9$=ID2>=;b"Eظޞȫ8Q&\N3`&R zD.b ryUkBȫ#A8$o!l]BC~co=D5ck-O5G <bj:ޘMk~78;njU*2Ŝ1^b3&t6BMSP31"sُ ^jVc 3w kbL$/ bLt9F1q!NCsRܛr5$D<|%g8B ?x~g[&&3uIbK_ޔIѥwW`PH"3.vlʹ՘p޾|$~SO ~ՕDr4)VáM?7f" Ǚ}fE;'c̕3ͨ[6.n{&R SƄ~2:o4Ϭ.qM b݁"1xQܐ:u3+cEpvOe%op$ۅ |;W_WpJU.{D|g}Ur?<fQ{<ɹᚚ uOK{D@~gdo TMK66ELؙ̟ѕ>)pkjV = >$w]YYHe[#I^ٙ'8@ ɐ3_'-:@nB~K |[DJWJz/M&S-Zn/ %G 3,yzLJo#e (n=MIbC=E6 gsJ)tiԕ%Ұ'Z!%K'"%U]8RƲk ɳ&NN G&'%JJ4KQc'$Qkj)iZX]C"!dOO6&'Q@[2 ]1&Q`er d5bI:JHEE9U D`8  _<|4}Jzkk$D*Z%7G]I/ʑ6͵s(t nfA1b-k`fa;4D`4B7;kIm&܍;'!P~WUfZSB"tZ #ZW*f!: =M|Y_>¹a.\b1X `5jӴ)a%9\Ke}N1ŎIRS<5i2dEwSRt˟ k{,%vo\`2 !: eR|c[m=mC=OF|\@᥺Nd߻ӆxrS][߰Y'gʒQOvS4 ֽy{ޫ=& gϲ~tt[<67r ^oɡ#Vw;gR<ga=Ihw8 8,~Rإuyƽ~Ιn%iDTHp L8;P*q~j=A:un)S"`#Ojk2p|!M.$!Z;Orכ3u1qa~V_l\<ȌxQ8Q䀹_hZ}lոݑϳ˗F=$;gGܙY[11^ kI4z(17&gba {1i[tӼ" ե:v@O:E7(z4HD VW20J׉F%Ɔ~0 D3BlA=cVlR#jz7e'y}S Ca7sdj&:ÄPzKd?΅3[4t['ppN܁`=Rj7V !E\[|h(UUM I;?활pև NM/O nC0__ e=WY)0jV 4@Β-Ę 4t _;*b$BCk ܃qKr] H~ K2Lh+Bt H? Z/Ht yOxy%8{7qH)(=[o"&֍1'|PJ³qb"y#PGIJt)V"vBn0VI^[χ=V7HpB?e/k s pi@'5?ud]osx0ҘWNtyctIvʻ:2CC(qޤ*?#U9g)vh2zI]"rx;W.l=iUQ0UʪvDO)ըUթ [Ȇ4ŷŠ oZd V>p(x!bc}Bu(E УN^kP\B6"5W!/FGI>/kl [Qya1Ā IC&2>ytܼtK Vʥ]DE&=zFRhp{VYRzz.X؞GH%8KGHL;I4:K??&$ q Vύе&%fQKx(t 'E7o-lxìi EF$&][\VQMѕ ȅr}3EK,=HP\VKO*N_LXG)1+VX Wgӎ .U1&7ggk++z WᾸs3V`( *0?ue@} <"e_C3%}u R+ K*[I?(OF>wf1bt7_EFM 4@8x{#sx~#pXE#|Sه]`0r~B7v%eI%e_&!e挌dC>֑^; ;I 땲}ґ(;Ix{= k@'흵.A#Ё6N1iPb_U{ Vr322X-p?{r(so[<58 &CtWrRM aaâPBzKr?Jsv뀥t'A5]$ :vl1#*l[x1UO)Ld_auGq,5xɽ;UID1_h"߫5+(Ϋa3A땸s|[\k$BypM2lōwc8qd|UhD *@<;Pˌ|A]`N {KqcNpn>%fHkW!$rX(XJ[^CDcwc+pS=+cs]mx}ReWD:TVX  `e_]3PCՁm<ť2c54 sZxMM~aBSMd]ёZ-6Чp zN tB-2a]3(y8)PH{"`Ƌ'OeurўrAP>d` atiƄQ[i\XNKng$ u,{ayڃIu8.3v9-%˷=>\$[I#)ǷJ GІ; ECTV-VX k| o1{ ϕ^GȝQfRmEkj+ƀ:~&-X O&O_>5o@KUXû2).#(*͔\_^rv cL)xAK8<7([R xj, +0!KHfoS**s-gLFx#!TXeD*AM#w<5jVN_;ЧXork1]K'|ژ(,>zZv}ʮ|dm_Rw[/钛3 !V95=ykr'ϥ=lAMhrrP,-|eC"nڰpE C?r_!%Fq l8|^Y `5j4|^[o H{Kv K1atg2,6e;֊޹(cƒȓ6CZu]>JG!IDbB ,&I/hVBj*ۈxwhW_)1>@XT+Fݩ- ]?Kcߥ`8H*߄0[L1z8NzٵUՁ{P}.[!͝et2yޙT9MbEB fEA5!yه -Ve1 kK\4S muB6zN?hk˪7c$ F\q00dVsN>(Cw`IUMhrB3n1zs")چv+#\&n둡I]p`Oځ>`zrԊ>=L7sݣqfxۚ2~/ySNn"-[5\;յit~=(p?[9Ssj3W`FyOlJĜxyƹҴDeY{άft۴h\bG$Ӫ0Nܼp/DD{D<ӨQ֣t..+n<@F,{jV&2clOt W+s41kY.An6: g='2-C9mEҴڪ[a_ʎem0:dmP -!@Y]KtjGgt`<s>g{zj kTxaڌ+uφ2'm!/ׯDVs `*W "úKSaN[TxOmEb2hF O #s#L 荴xױK遲 vOW* HCBL^P|8Qi Dk̈́̽wsMǫMp;2X]UNڴ! ;hfH"}Acy+WO~ 1\JVÑ+t]B/NMvFe>Yŝ@VU(4)EF윕 KZ p;o fSK9,34]h:|H%ŰB1lt3ɽ{bpD2ZmNBR  j9BXׅ}uz@V.$$]}"izI+F8Q.P P0XNl'H=7΅Ε鷈qB|&:}aM1?]Vq$*gF{_#c"9̖yаgV|{d$ݭ[73ёF'G%:yxnPIkCa=;pO+E`q!Bτ:yfNY `5j@_q#3OD.RV̸AUJ$'o.]!.V({m>i,I-j&s/;pVޗB 8mF< 92n$vw~dBR^'P",' }[)ݭ9 Edit:%t~Z/--.54ά dFi\,ꢉs-D́LV~܎b Gnj˥ bSex>c~# ;BayمQH(A=W[V}y-Rmѫؖ>G S>p@>4Zr8wFHQDO]'_R{o~]*&VD-w9pEx缰]N<(!l(¤]ḍOvI* ל#3>fc=h*/ |]]$!SEػ/F Z6DJɾ#x\GKvtbaE#YF`&TbIL5O[DhT۟+Lrry744r9('mLbgL35{ӳy^\u 8vlYI4̷76"%<<)D2}JmOp榦8q qHV\[0)zFpS  !t8dZ)8X=p3ў$P>=6RŶ75mWVqBu8(E΍s[RQQٰXvܧwa%_oWˀZ.^ JWJRUptkEP-Ek$IޠAkR"%s ډAt47V.!>x29qxX: O*̽3XF1BYX8XQx: x6a?VX _[K" MM8;q:ېF#dt ?>Z8ȼp.4ӹkucԶ2*'S7t0ڽۀ&`_lҭq[&rBo} 3L{{qYx9{IiR2&af8CFH"~؎15$D7l^ƏIIQ139RF-+͐aソT0w;Lҡ A9F:\i[ˊ7B*щ%"Dl/u^p 9 ;-|5Fz#Ҝ;[fPNdMRr{2Z?FVbxl^bkFt{i&M"mi C.橀R 23k~LϴLiu5lUEW<#B_程ʖa TٗM-4.A`=h O\"] ۺK[+/dسDx6"nq~NԪ|rC:ݽuCv\Y?YAxF3!izDDw}:©5챯oByMxA(L6mt:o2?`Ԏs3~QԱ'80&Ft Z\;bݠnqsIK {'GOUP$LUc+ 7yT=XMu!(1_U!O qK.6zG>5}-.q qbAەH?Z5C0V*Hs۾h}?iM#h܆֛@1y==|ި`T=Uh uOoсۨ3<*qThRsVQ5'&SzrV2U;_BJi h~^ŎT{NqDŽr$J:fo#|LoS^4;77'etG{>H4%QBZ9;;ж+6H7[4H`16hC?q|ӳ+˜WZUm awWu24q dr țϮ˽xn](pr5}0RBZen(X:olɫ "OB7M~&?w>^rMڥㄜ=x2ZjZ(Lɳ7qtODnjr?z`F([^(_,[[ۤZhe2a ߝK\>w%LFZ/Z-4G34ʮ)][;8a*#"*ڤ2F[9Y{=@L,Yۇ&Kg"D4|-ڂW'4fZ 9,H+[+jM÷PÂ8[`ZJ)Jv|Хzѽ!R(V6}e5ቛ7a\zzzGTn^FccX `5jAo9rHpft !ni?ztg$w2"Flܶ,%d$T0yMcii%-y>=s'SD^s-,\m+Bѹg%χ|ӽ=IȐk*cJ}E(`t KP$ /W{d#|^4=S@*J)K5~B&d" ϾaY!Bˀvt e4ԦgZa_Uv>Lxzee"Y{FXxyaԓ'ȯ)NH7wۆ!;~YķB]lc~g0 h@-ՏJ67 S ""}kLD+T<ݽ0;`{P=򥿾 ܱ ,: &'¢sWTݹ('CiEE_J{h*{CaED`n&b1٪}_:^YxnTOI^ h?Of䔇B>`%d5j`)>zkz.mp溇7QHg}?cQaS9u*Ô!Z|n]~u_QK54M5kyMë3V-@\'N[qx ;%.A 2S1$wO/Y zxޫEd$foX x`4Lƣt}kaR++30pr᎐G.yn h@{i< +oG;4 6gq`0_UYHlTs̽whm?*YpN)zbr/~xlVʱuz/h̑S~E4VpVUSSCXʘ0VӀYƇwy̩ҳ ؎xxԈ{H7$v!#  <gE`5joA#I^Љą?](7w֌U{ȞԄ]z6x.1 yպKUsXX XiUfjVX `5j@F$S;7o'W,Ij79jIMx+%*`6(WݚEyQaQM nʷA?A.V[ZDBa8ڰO+p@1Qg ܫ}{7zt S^dӚ=b8$,幹w%JG؄QteX5tRrq՜6bZnvn(Ͽ o-K*Jrvcy^Y WyX `5jVX `50` 7IRx?Uіm\~M3_IGޝY~^:1:ʕX&;`h=yu*>0MDr.pzC/L6* @syw! to,7b+^8:֝:7k;&zQILJpq+Ztm'L:y-?O,7?61YUΫt YVfnjXVX `5jVX 0 T]]mV`ވ蒃t 3,ѮIug)t &৶!8>iIiD yHȳo8<7)$/1bc?e;D;fϏBq'>dZgy3Htkq- y4݆-? $|Eׂ#k}pYDOסKIW\4'F;ZHQBOd$Jq~V̨ւiTUvv? :xx9z o]': XG\]j"ڎBCD]kOGZkg0Yif$T;K{ziwfs͘ XɥbV`efI6BIY `5jĘܬHI$I;m}M ~0Mrxey18"xYa}[ϏuO?ƛbUrvy;dڪ)EZ9=`2`/v!ڸE͂ !~Dq:+;nc#&'$%[}B}s/oϤ5^y!O]}DXc&#ݳ biَÛ)˦r mgns0/zX  iU޾h1^;-3nM~x?8N}kϜ)Rt:ybphD/k6HB 4gEWgpLY9yhRZ/#Ș}+PYyfcͮYAp "q_8Ⱦ^#97աoȈ1,ͮ3TBAuf͞ːVM<- S$bO? soMMy  e'/.q .(EWϓ:t/|.={'xJwiJ +UrBj9h!|2E~ޜq%d~ROd⻯ladzm\Op C[SgY13{ 0ݳPBV%\.I/|fxowӫ%H0:oIo=r 25Ƅ 0Tg#=HI1!iL,6,x}7أӶӺf]2G 1xs镎vU;w)#^8o3GL ;Hp[X VX prY* l=cס 8LھH.Aڮf)N1@!d %&5cs`;l7mׄ@w/s+2H@lԩ jB]`91 @^ջ6,SF֓>VE,485[د\4`i}aGa?}ςc!Zdi=Ǜ[U$sseJBFcSaʻs8,w1B~T=T8m yVc"/ln `Qk@*\έL ;`eiQAbť5aY `5j0pLw*K,atS@؞6 ~]q)*U3,ML'(iY<2C]xt덫 i*W:tCXAV"<7 rKh `50`&3)\_316C VR'A6enr}scKI_4\Km f5uΘ1! NfP,]"pFW7>P*F>pfItZSYFD?"t z $qTS6S7ZT 1:hhށʾiVΏVnڬ< [k]pG[u%twAX㭾p88ϋwaRqᅣ7|B͎?m0E3OU^j:9dJ?oƬ<+W%?a_{=5L\ 2s;#SI&wrpx QɊ3ݝځk#τDnm/,:ae?|,3{DWODA>syp1KCԌ8]Y|C.[KKB>8hoC-̭C炲ts_?;@UAҸ8{];-]ө%_ogF(+9MrRZ| *5Li: {f<ZX=H-W*eQTߩ̹USpM `>{?нr.T>:ϩh~i, rʼMK=L; xQ$rk9;srԠRs:ЅcAt27Wcsqs;'0b2& +yXʛIt;Sd[Itq{z4 -q )/\Ǡ/ iUXOggb.qkDDs9t V~p>&܊(tI0U#f,ǩDGTݢVDd♐)$i-%U+? o y):ţ9IDATGY%A|(D!ѥ$Bg HŠQR}+>vc_l(yVd+p|1. {?.yINY @ ۻr—-9s1hdT,jC) ~c!|׍S܀7)]j@MpjGO'Dnr|RWԴC@Hp<}pafu)(]"Zm_]z|I3cƴ䍆| eΦ 1kjlܼ-ȀЀH/G9 KveI@Sic[Bc۞{ {Ku-E X `5.aw/ZՑ3 +6J6Y?,Eoxn''b'_`'*>mpn,d^0y $L:9Plqs~|W==ΩF{/X1qi&4,:S}|S}!L]䮳fzү7HB!^sľ)o}ZAWwx QWĻS6|dzkc~,GC h~8s#rEGR海0FڐB^"A铃DPcV>!bc}B%l0.!66.1NW|ksGn#z%2]F1oQIz^?F>Cղy)mde1QwusolOd[8Dz0嵿nO~t ]8ӉY˝^0ҥ?%nUSq6Х ߹tlz!bF9`u*1LK^͗bp_=Wϐ|P_jA֒"Lx$ 7C񩲮u1+fdd`Çm+i]1 >&nJ&+Ņ<,y$ӹ˪0mU 3rz ;|="OS\XsZ,! u#E^Y `5;1cR}~ħDnKG z|Q(jMQtc0fi 7Z\~X[n`dp@ٝ" lm, e< Ʌ w)+6- l}vV|-}_ [VVP''' G_ie5prs3j5|x RԈbԶ(qH+poK%m~z6Pi@c;]w@4䴌ɹ8XJs&V*]q+ *K %2FȮpx+tg{et@r K3>zs_nnqa {ucCG \mӐ{}a#M}Ѡ\OܯmH,;|dW5<V?;Nbt)1aʇODž:V>+aylqfj4Aj޴Ł=+Xû2 IYX ׀܌7d!7ߢcc}ag#?=bD|fFa} lf̫6>AN} Ck>CE|yFm7;n#hs fQ(7*߄0RPdtv!δ7S!+bp]Q}MMcq}HJ,?M+z:8rv$ IUNbΖA[`&h~N'BhX7Ӡ+ptTU D@Ò]DvCd |;\Wb!4“4u .mÕgR\y5 !Oćff "?ڃ+{,[?I(}5] 4GHh*Q;h*΂<9.>&9E0aVX ܣ>qoJx^3 {7ӤG&-(BLleZp, eHwY̢lάX `[?&AN;Iz%ívȀT]m=f2%. e}8}]Љ|ŻӞK<ʷ܄w3Uo7U3C' %ODYhotaѤֿN~~W_B%1;tP|PmA#\r(Gb8e&u\u.GR8G$N,@6>@f/++#|{4M6@Ϊ4j9Enc.5JhG`U>z7(JݳK fe1C3,*poU?ISKGmUXiaFsylfVVGb!O0[|`V̢k̺1`~2so=|5%!/ν7IT+;pn)( ˨x!Ih|ԉ^G:'=ݽV96/*;Z}RfC1}#?ʉ n(? BlpOU~C "_ٺ|}>R\ΥH*\fb5 Qd i4񝟋EX8Nfjd+4:^k3%)@]A{MU]NqhzQ_*7Yu)Ay,1Qw6s ΟOu+C-GI[[ۍD'z <//pX `5jkmt/\7<2sf]zla~PIX E06Ȉ)n]iYpXu6Y~| ],&vfLFo" Stbɪ`%j"]55x3V όsXR:5ÊhVH{'e͆lpj9R<@'^32z`sBH㔎tˆ=ʐzd6|r3#6122GzpM]8,xAd_6a=6A3(zŲ9~~xi񮓨?S&S64A|֔=ş9S~~Y/fl6>|[J EEb3+tE?:%£$    }?"ZXXSrTX7>5v[T.jjǺ1֍tQDǙ[f22}/zMv w]·}c7߽b/Q 㺉nU aF" ͱGUˆrr*a34\31Ձµam65Xmpf4mmַ47wA +="ѽze]ons4^\zzYk&G9٘F` H,Vi`xxWv3 C`I:7''wA -E`Æ >˷To*U *{ KOӻzG}FAc2Ω U _r<%j泿i%\ŞwVu=*ZK8Fӵww"b/r.QNqC7 48\ܫ_Bui64 /BxEITjz]N%I>8^[7_-\ /mK=;"aJ"gXĤ5e0=R~}hL^a %]SN@@@@@6$ ޻X.oaH ŀ>quK찻$F(yGQ"33Dޜ=RFwwA@@@@?^kl0>D@5& HZQ7YM計y$-$><5zo     `O߷z DEǻ #-<7 -@ S3F        70P  [/Եeaue&oΣ     fp0 A@ohݷ]CHp0y#bMKli&θ7!I멲6s;.)RJ7K#$HQ-GkʫN0)ţ6{EsmvPhOMZNbu, ZgoÞT?ͶK;Р̇w= {- Donxnϕ% =M=쵊C婖klfθSLoPn{uN&ML>k{̵{6*Z7wYZgoǿ8 W3$,sij l<[x(8 p^mr77KکIk-az{EU$c㩽[7VIM0a     7[l9w\^^Ȇ }J )s;XƒmF"xsҒ+_"d,Y?Ezn/k֦%_aN耨 vgNltfqޥC~dSU1Uf bl gA*Z"*cp.gI"MK3ߎ .t q4YR]f:ѩk.# V$w5Dgnz7%g3mT"L/l`**Z}*+)>gCYPh=p#!Ή3n r"FjۨJ;AC%ĽɢKZλ|E{wk*GXfP@HRp|6%%vT}iJw)=ez*Z(1V rji]3E..* ē]GuJ=>jhGt~Bx %+ jHue0aGλ;mZmSYYFք|̔D-&¼w _E(Ƕ ~JD"7 _[P&ϗGI2|_0z榫-L?95-ץJE=Jxuo     91r O8j)݊<);1mp.a=E-KjMS hӏ .&;o퇟l䓵~{xy5]Uw$\BeUFe     7=:f5؏ ^JhCQe@()7QcpnK;l5)닙}m+'r\g¦dYW[-`F3Zc\0 R&W&饑RmzBʢG;W}>]bmUC+tꈱ]ȐuMIw:[fkl̶J?d5fII!^,6ߛܕVqM5 :2+.&k;Aq@[:ل ID xO6׭/j%b1)4}OnrR>4.B8k q9w'v_R3C;fDg+o4f2cu[MN@7oƯ sj|si{i+~`ܐ5d'qMq~F4V9zo&EivuRáS3(jcDIH8TRLQۋDGhF{(j٤E&'GvN&v{Ӿ;<JtPmt/w      ppFxsg-esP r s&^gZΟhx|fsl8t1DNAzhY(EuWIRWʺE*:\篌jU_^QTA8 EuU扞S~yI- ^x7~YԕewV| ߓM3 WvU/m$ R`/UxFvM>LzǫJ9}kn~>~}>m$oJp6^65H4qU.N"|:#"C10,UĹU;I?1y~g2jl_hn,P.Gd z{=H1jܳ߶6]rQ4((Bm |EĜ9H=uE0B嗯oKڟ'Y{bKKsM j kS% ӦSL?R Lvmܐa+S+4ԭSM^e<<7nsߦdHU1344v?xy*04y* O/]["`wH/FG>S3Kotw~^aS5 2@@@@@8^܀+SٶO$mm̲y|y47FTTYXn*aRT#KR+{9~{VޜaPáh{|FNJ*mO|pRcWV H)sc] (5٩~Ժ\G[$C2aSBWETs-"gy̨ vkLq烇 \1ovjX"f.Q}y }ձ1d#E,]FاqçʞЫh\F:ɼ-Y#!׫)/ƭt!G7;8C?sO}zkS;nc{E%EϠi("XYCuh c?*TP@C%t4St7l5;WjO"     \|EZ͑kщiV uE2vWYUY2zyr.[\(ޱ.M`hj79WUn2dH`O^) /.Pe~.:A2.]Rfħy`DLWzl{_b R~" P M\4qI NHMϒ&pُYt ~]gDVzW9﨓i.{9ތ~]ZccRҗmNc9s^N9պ/ѿgkh-G 8$_An hS35ޝ5 /?==_j3^igáb郟~+t)R/^G0;{b[˘&mZq`3qNZjtAr3SB"gylWdQJ:$ :Z $z@ sHY?uda c^0x˿2b5IɫN=E!qvb:ĘI_z5&)ӍS$tDbsYO'ؾɇNzq?H EugQƒ+DF>~>orLT%Ҏ7蒘ɣyGײh[X-J.wOvmGrWxuvC<8lVJ⢍2w+ڸd9g鲜ȧYT6~gȑ]No#˛ 0nsO8PTq 䛠?d'?s߾ۑL5GQd,XJq\߼gI %hxW>9훢2s=c<@Agbz%P](G"%pN*82Un} Ў jV(;GMjg|mɢח^&t 75Y kˬG$J)mg4$!w*4dѠ߾~`mOχlœPp/ iCk:.Uȫ*×]͊'RH[X*qpݚ Ǐ!WH-z*wcb~Vź乳+mjm,gx郦ٻd3/, ZBMXTbm8\*ݖ^$qr9yv~ɈEW7|)t2}W6mh+Zh|^8k*Y.ɵ9\XE],ڌhͲ6AjG _*.1SE ƻKݫAݪ     O ÷_oǟTZHhnPHo4v>wC,d+q[.}][6B5q(TzFf^CICH:)SeÃSWƭ/oTZ&󺵻?@z-sO**! ?u@@@@@@q3R+x(#YC{q<&lDaca I e=DnHxȄlNJΟãN2dqy S)H1n"J-QlwޒũONOuD-KsӪ\抎2wߍw?!hDW2x›_w$"S'PDNzci& =Cw)>rΦiKK5Ꞓvar.}iQ9/5ipODZjgx'+wŔ<\гY9)j~J0Sxزe˹sD(,N"?*,Ki.jm ljV̾xYT[aq>6s[DXK5o ^jio%136<4Z܅JTVScSAO~@iNDnIVK4(:N)ㆢs!,KR*%[osFtS⦅3DT>)zMv er;{ZbEWwK%(vDUgj* 81AA:A:AcY9\;ovr_MDTvp!t[I@8%cE C @@@@@@;' @@@@@@@@<&hTD&OT        LQA@@@@@@@@8bunyan(1) man page node-bunyan-2.0.5/examples/000077500000000000000000000000001377616564400155765ustar00rootroot00000000000000node-bunyan-2.0.5/examples/err.js000066400000000000000000000034221377616564400167250ustar00rootroot00000000000000// Example logging an error: var http = require('http'); var bunyan = require('../lib/bunyan'); var util = require('util'); var log = bunyan.createLogger({ name: 'myserver', serializers: { err: bunyan.stdSerializers.err, // <--- use this } }); try { throw new TypeError('boom'); } catch (err) { log.warn({err: err}, 'operation went boom: %s', err) // <--- here } log.info(new TypeError('how about this?')) // <--- alternatively this try { throw 'boom string'; } catch (err) { log.error(err) } /* BEGIN JSSTYLED */ /** * * $ node err.js | ../bin/bunyan -j * { * "name": "myserver", * "hostname": "banana.local", * "err": { * "stack": "TypeError: boom\n at Object. (/Users/trentm/tm/node-bunyan/examples/err.js:15:9)\n at Module._compile (module.js:411:26)\n at Object..js (module.js:417:10)\n at Module.load (module.js:343:31)\n at Function._load (module.js:302:12)\n at Array.0 (module.js:430:10)\n at EventEmitter._tickCallback (node.js:126:26)", * "name": "TypeError", * "message": "boom" * }, * "level": 4, * "msg": "operation went boom: TypeError: boom", * "time": "2012-02-02T04:42:53.206Z", * "v": 0 * } * $ node err.js | ../bin/bunyan * [2012-02-02T05:02:39.412Z] WARN: myserver on banana.local: operation went boom: TypeError: boom * TypeError: boom * at Object. (/Users/trentm/tm/node-bunyan/examples/err.js:15:9) * at Module._compile (module.js:411:26) * at Object..js (module.js:417:10) * at Module.load (module.js:343:31) * at Function._load (module.js:302:12) * at Array.0 (module.js:430:10) * at EventEmitter._tickCallback (node.js:126:26) * */ /* END JSSTYLED */ node-bunyan-2.0.5/examples/handle-fs-error.js000066400000000000000000000022221377616564400211220ustar00rootroot00000000000000// Example handling an fs error for a Bunyan-created // stream: we create a logger to a file that is read-only. var fs = require('fs'); var path = require('path'); var bunyan = require('../lib/bunyan'); var FILENAME = 'handle-fs-error.log'; var S_IWUSR = 00200; // mask for owner write permission in stat mode console.warn('- Log file is "%s".', FILENAME); if (!path.existsSync(FILENAME)) { console.warn('- Touch log file.'); fs.writeFileSync(FILENAME, 'touch\n'); } if (fs.statSync(FILENAME).mode & S_IWUSR) { console.warn('- Make log file read-only.'); fs.chmodSync(FILENAME, 0444); } console.warn('- Create logger.'); var log = bunyan.createLogger({ name: 'handle-fs-error', streams: [ {path: FILENAME} ] }); log.on('error', function (err) { console.warn('- The logger emitted an error:', err); }); console.warn('- Call log.info(...).'); log.info('info log message'); console.warn('- Called log.info(...).'); setTimeout(function () { console.warn('- Call log.warn(...).'); log.warn('warn log message'); console.warn('- Called log.warn(...).'); }, 1000); node-bunyan-2.0.5/examples/hi.js000066400000000000000000000015011377616564400165310ustar00rootroot00000000000000var bunyan = require('../lib/bunyan'); // Basic usage. var log = bunyan.createLogger({name: 'myapp', level: 'info', src: true}); // isInfoEnabled replacement console.log('log.info() is:', log.info()) // `util.format`-based printf handling log.info('hi'); log.info('hi', 'trent'); log.info('hi %s there', true); // First arg as an object adds fields to the log record. log.info({foo:'bar', multiline:'one\ntwo\nthree'}, 'hi %d', 1, 'two', 3); // Shows `log.child(...)` to specialize a logger for a sub-component. console.log('\n') function Wuzzle(options) { this.log = options.log; this.log.info('creating a wuzzle') } Wuzzle.prototype.woos = function () { this.log.warn('This wuzzle is woosey.') } var wuzzle = new Wuzzle({log: log.child({component: 'wuzzle'})}); wuzzle.woos(); log.info('done with the wuzzle') node-bunyan-2.0.5/examples/level.js000066400000000000000000000021221377616564400172400ustar00rootroot00000000000000// Play with setting levels. // // TODO: put this in a damn test suite var bunyan = require('../lib/bunyan'), DEBUG = bunyan.DEBUG, INFO = bunyan.INFO, WARN = bunyan.WARN; var assert = require('assert'); // Basic usage. var log = bunyan.createLogger({ name: 'example-level', streams: [ { name: 'stdout', stream: process.stdout, level: 'debug' }, { name: 'stderr', stream: process.stderr } ] }); assert.equal(log.level(), DEBUG); assert.equal(log.levels()[0], DEBUG); assert.equal(log.levels()[1], INFO); assert.equal(log.levels(0), DEBUG); assert.equal(log.levels(1), INFO); assert.equal(log.levels('stdout'), DEBUG) try { log.levels('foo') } catch (e) { assert.ok(e.message.indexOf('name') !== -1) } log.trace('no one should see this') log.debug('should see this once (on stdout)') log.info('should see this twice') log.levels('stdout', INFO) log.debug('no one should see this either') log.level('trace') log.trace('should see this twice as 4th and 5th emitted log messages') node-bunyan-2.0.5/examples/log-undefined-values.js000066400000000000000000000037241377616564400221570ustar00rootroot00000000000000#!/usr/bin/env node /* BEGIN JSSTYLED */ /** * is a change to add a * feature to Bunyan's log record stringification to log `undefined` values. * Let's attempt that with a custom raw stream. * * Note that a raw stream here isn't ideal, because using a custom raw stream * means that it is a pain to use some of the other built-in stream types * (file, rotating-file). However, it might be a satisfactory workaround for * some. * * Example: * $ node log-undefined-values.js * {"name":"log-undefined-values","hostname":"danger0.local","pid":28161,"level":30,"anull":null,"aundef":"[Undefined]","anum":42,"astr":"foo","msg":"hi","time":"2017-03-04T20:53:54.331Z","v":0} * $ node log-undefined-values.js | bunyan * [2017-03-04T20:54:41.874Z] INFO: log-undefined-values/28194 on danger0.local: hi (anull=null, aundef=[Undefined], anum=42, astr=foo) */ /* END JSSTYLED */ var bunyan = require('../lib/bunyan'); var fs = require('fs'); function replacer() { // Note: If node > 0.10, then could use Set here (see `safeCyclesSet()` // in bunyan.js) for a performance improvement. var seen = []; return function (key, val) { if (val === undefined) { return '[Undefined]'; } else if (!val || typeof (val) !== 'object') { return val; } if (seen.indexOf(val) !== -1) { return '[Circular]'; } seen.push(val); return val; }; } function LogUndefinedValuesStream(stream) { this.stream = stream; } LogUndefinedValuesStream.prototype.write = function (rec) { var str = JSON.stringify(rec, replacer()) + '\n'; this.stream.write(str); } var log = bunyan.createLogger({ name: 'log-undefined-values', streams: [ { level: 'info', type: 'raw', stream: new LogUndefinedValuesStream(process.stdout) } ] }); log.info({anull: null, aundef: undefined, anum: 42, astr: 'foo'}, 'hi'); node-bunyan-2.0.5/examples/long-running.js000066400000000000000000000033531377616564400205550ustar00rootroot00000000000000/* * A long-running process that does some periodic logging. Use bunyan with * it some of these ways: * * 1. Direct piping: * node long-running.js | bunyan * 2. Logging to file (e.g. if run via a service system like upstart or * illumos' SMF that sends std output to a log file), then tail -f that * log file. * node long-running.js > long-running.log 2>&1 * tail -f long-running.log | bunyan * 3. Dtrace to watch the logging. This has the bonus of being able to watch * all log levels... even if not normally emitted. * node long-running.js > long-running.log 2>&1 * bunyan -p $(head -1 long-running.log | json pid) * */ var fs = require('fs'); var bunyan = require('../lib/bunyan'); function randint(n) { return Math.floor(Math.random() * n); } function randchoice(array) { return array[randint(array.length)]; } //---- mainline var words = fs.readFileSync( __dirname + '/long-running.js', 'utf8').split(/\s+/); var levels = ['trace', 'debug', 'info', 'warn', 'error', 'fatal']; var timeout; var log = bunyan.createLogger({name: 'lr', level: 'debug'}); // We're logging to stdout. Let's exit gracefully on EPIPE. E.g. if piped // to `head` which will close after N lines. process.stdout.on('error', function (err) { if (err.code === 'EPIPE') { process.exit(0); } }) function logOne() { var level = randchoice(levels); var msg = [randchoice(words), randchoice(words)].join(' '); var delay = randint(300); //console.warn('long-running about to log.%s(..., "%s")', level, msg) log[level]({'word': randchoice(words), 'delay': delay}, msg); timeout = setTimeout(logOne, delay); } log.info('hi, this is the start'); timeout = setTimeout(logOne, 1000); node-bunyan-2.0.5/examples/multi.js000066400000000000000000000006201377616564400172640ustar00rootroot00000000000000var bunyan = require('../lib/bunyan'); log = bunyan.createLogger({ name: 'amon', streams: [ { level: 'info', stream: process.stdout, }, { level: 'error', path: 'multi.log' } ] }); log.debug('hi nobody on debug'); log.info('hi stdout on info'); log.error('hi both on error'); log.fatal('hi both on fatal'); node-bunyan-2.0.5/examples/mute-by-envvars-stream.js000066400000000000000000000057671377616564400225100ustar00rootroot00000000000000/* * Example of a MuteByEnvVars Bunyan stream to mute log records matching some * envvars. I.e. as a way to do: * https://github.com/trentm/node-bunyan/issues/175 * https://github.com/trentm/node-bunyan/pull/176 * outside of core. * * Usage: * $ node mute-by-envvars-stream.js * {"name":"mute-by-envvars-stream",...,"msg":"hi raw stream"} * {"name":"mute-by-envvars-stream",...,"foo":"bar","msg":"added \"foo\" key"} * * $ BUNYAN_MUTE_foo=bar node mute-by-envvars-stream.js * {"name":"mute-by-envvars-stream",...,"msg":"hi raw stream"} * * Dev Notes: * - This currently treats all 'BUNYAN_MUTE_foo=bar' envvar values as strings. * That might not be desired. * - This is a quick implementation: inefficient and not well tested. * - Granted that Bunyan streams are hard to compose. For example, using * `MuteByEnvVars` to be a filter before writing logs to a *file* is a pain * for the file open/close handling. It would be nicer if Bunyan had a * pipeline of "filters" (more like core node.js streams). */ var bunyan = require('../lib/bunyan'); function MuteByEnvVars(opts) { opts = opts || {}; this.stream = opts.stream || process.stdout; var PREFIX = 'BUNYAN_MUTE_'; // Process the env once. this.mutes = {}; for (k in process.env) { if (k.indexOf(PREFIX) === 0) { this.mutes[k.slice(PREFIX.length)] = process.env[k]; } } } /** * Returns the given object's "o" property named by "s" using the dot notation. * * this({ name: { first: "value" } }, name.first) == "value" * * This is a verbatin copy of http://stackoverflow.com/a/6491621/433814 * * @param o {object} is an object. * @param s (string} is the string in the "dot" notation. */ MuteByEnvVars.prototype._objectFromDotNotation = function (o, s) { s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties s = s.replace(/^\./, ''); // strip leading dot var a = s.split('.'); while (a.length) { var n = a.shift(); if (n in o) { o = o[n]; } else { return; } } return o; } MuteByEnvVars.prototype.write = function (rec) { if (typeof (rec) !== 'object') { console.error('error: MuteByEnvVars raw stream got a non-object ' + 'record: %j', rec); return; } var muteRec = false; var keys = Object.keys(this.mutes); for (var i = 0; i < keys.length; i++) { var k = keys[i]; var match = this._objectFromDotNotation(rec, k); if (match === this.mutes[k]) { muteRec = true; break; } } if (!muteRec) { this.stream.write(JSON.stringify(rec) + '\n'); } } // ---- example usage of the MuteByEnvVars stream var log = bunyan.createLogger({ name: 'mute-by-envvars-stream', streams: [ { level: 'info', stream: new MuteByEnvVars(), type: 'raw' }, ] }); log.info('hi raw stream'); log.info({foo: 'bar'}, 'added "foo" key'); node-bunyan-2.0.5/examples/raw-stream.js000066400000000000000000000016271377616564400202240ustar00rootroot00000000000000// Example of a "raw" stream in a Bunyan Logger. A raw stream is one to // which log record *objects* are written instead of the JSON-serialized // string. var bunyan = require('../lib/bunyan'); /** * A raw Bunyan Logger stream object. It takes raw log records and writes * them to stdout with an added "yo": "yo" field. */ function MyRawStream() {} MyRawStream.prototype.write = function (rec) { if (typeof (rec) !== 'object') { console.error('error: raw stream got a non-object record: %j', rec) } else { rec.yo = 'yo'; process.stdout.write(JSON.stringify(rec) + '\n'); } } // A Logger using the raw stream. var log = bunyan.createLogger({ name: 'raw-example', streams: [ { level: 'info', stream: new MyRawStream(), type: 'raw' }, ] }); log.info('hi raw stream'); log.info({foo: 'bar'}, 'added "foo" key'); node-bunyan-2.0.5/examples/ringbuffer.js000066400000000000000000000005751377616564400202740ustar00rootroot00000000000000/* Create a ring buffer that stores the last 100 records. */ var bunyan = require('..'); var ringbuffer = new bunyan.RingBuffer({ limit: 100 }); var log = new bunyan({ name: 'foo', streams: [ { type: 'raw', stream: ringbuffer, level: 'debug' } ] }); log.info('hello world'); console.log(ringbuffer.records); node-bunyan-2.0.5/examples/rot-specific-levels.js000066400000000000000000000030141377616564400220110ustar00rootroot00000000000000var bunyan = require('./'), safeCycles = bunyan.safeCycles; var util = require('util'); function SpecificLevelStream(levels, stream) { var self = this; this.levels = {}; levels.forEach(function (lvl) { self.levels[bunyan.resolveLevel(lvl)] = true; }); this.stream = stream; } SpecificLevelStream.prototype.write = function (rec) { if (this.levels[rec.level] !== undefined) { var str = JSON.stringify(rec, safeCycles()) + '\n'; this.stream.write(str); } } var log = bunyan.createLogger({ name: 'rot-specific-levels', streams: [ { type: 'raw', level: 'debug', stream: new SpecificLevelStream( ['debug'], new bunyan.RotatingFileStream({ path: './rot-specific-levels.debug.log', period: '3000ms', count: 10 }) ) }, { type: 'raw', level: 'info', stream: new SpecificLevelStream( ['info'], new bunyan.RotatingFileStream({ path: './rot-specific-levels.info.log', period: '3000ms', count: 10 }) ) } ] }); setInterval(function () { log.trace('hi on trace') // goes nowhere log.debug('hi on debug') // goes to rot-specific-levels.debug.log.* log.info('hi on info') // goes to rot-specific-levels.info.log.* }, 1000); node-bunyan-2.0.5/examples/server.js000066400000000000000000000043051377616564400174440ustar00rootroot00000000000000// Example logging HTTP server request and response objects. var http = require('http'); var bunyan = require('../lib/bunyan'); var log = bunyan.createLogger({ name: 'myserver', serializers: { req: bunyan.stdSerializers.req, res: bunyan.stdSerializers.res } }); var server = http.createServer(function (req, res) { log.info({req: req}, 'start request'); // <-- this is the guy we're testing res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); log.info({res: res}, 'done response'); // <-- this is the guy we're testing }); server.listen(1337, '127.0.0.1', function () { log.info('server listening'); var options = { port: 1337, hostname: '127.0.0.1', path: '/path?q=1#anchor', headers: { 'X-Hi': 'Mom' } }; var req = http.request(options); req.on('response', function (res) { res.on('end', function () { process.exit(); }) }); req.write('hi from the client'); req.end(); }); /* BEGIN JSSTYLED */ /** * * $ node server.js * {"service":"myserver","hostname":"banana.local","level":3,"msg":"server listening","time":"2012-02-02T05:32:13.257Z","v":0} * {"service":"myserver","hostname":"banana.local","req":{"method":"GET","url":"/path?q=1#anchor","headers":{"x-hi":"Mom","connection":"close"}},"level":3,"msg":"start request","time":"2012-02-02T05:32:13.260Z","v":0} * {"service":"myserver","hostname":"banana.local","res":{"statusCode":200,"_hasBody":true,"_header":"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nConnection: close\r\nTransfer-Encoding: chunked\r\n\r\n","_trailer":""},"level":3,"msg":"done response","time":"2012-02-02T05:32:13.261Z","v":0} * * $ node server.js | ../bin/bunyan * [2012-02-02T05:32:16.006Z] INFO: myserver on banana.local: server listening * [2012-02-02T05:32:16.010Z] INFO: myserver on banana.local: start request * GET /path?q=1#anchor * x-hi: Mom * connection: close * [2012-02-02T05:32:16.011Z] INFO: myserver on banana.local: done response * HTTP/1.1 200 OK * Content-Type: text/plain * Connection: close * Transfer-Encoding: chunked * (body) * */ /* END JSSTYLED */ node-bunyan-2.0.5/examples/specific-level-streams.js000066400000000000000000000040251377616564400225030ustar00rootroot00000000000000#!/usr/bin/env node /** * was a request to have * bunyan core support logging some levels to one stream and others to another, * *not* limited by bunyan's current support that a stream `level` implies * "that level and higher". * * Let's do that with a custom raw stream. */ var bunyan = require('../lib/bunyan'), safeCycles = bunyan.safeCycles; var fs = require('fs'); /** * Use case #1: cli tool that outputs errors on stderr and everything else on * stdout. * * First make a raw bunyan stream (i.e. an object with a `.write(rec)`). */ function SpecificLevelStream(levels, stream) { var self = this; this.levels = {}; levels.forEach(function (lvl) { self.levels[bunyan.resolveLevel(lvl)] = true; }); this.stream = stream; } SpecificLevelStream.prototype.write = function (rec) { if (this.levels[rec.level] !== undefined) { var str = JSON.stringify(rec, safeCycles()) + '\n'; this.stream.write(str); } } var log1 = bunyan.createLogger({ name: 'use-case-1', streams: [ { level: 'trace', type: 'raw', stream: new SpecificLevelStream( ['trace', 'debug', 'info', 'warn'], process.stdout) }, { level: 'error', type: 'raw', stream: new SpecificLevelStream( ['error'], process.stderr) } ] }); log1.info('hi at info level (this should be on stdout)'); log1.error('alert alert (this should be on stderr)'); /** * Use case #2: nginx-style logger that separates error- and access-logs */ var log2 = bunyan.createLogger({ name: 'use-case-2', streams: [ { level: 'info', type: 'raw', stream: new SpecificLevelStream( ['info'], fs.createWriteStream('specific-level-streams-http.log', {flags: 'a', encoding: 'utf8'})) }, { level: 'warn', path: 'specific-level-streams-http.err.log' } ] }); log2.info('200 GET /blah'); log2.error('500 GET /boom'); node-bunyan-2.0.5/examples/src.js000066400000000000000000000011571377616564400167270ustar00rootroot00000000000000// Show the usage of `src: true` config option to get log call source info in // log records (the `src` field). var bunyan = require('../lib/bunyan'); var log = bunyan.createLogger({name: 'src-example', src: true}); log.info('one'); log.info('two'); function doSomeFoo() { log.info({foo:'bar'}, 'three'); } doSomeFoo(); function Wuzzle(options) { this.log = options.log; this.log.info('creating a wuzzle') } Wuzzle.prototype.woos = function () { this.log.warn('This wuzzle is woosey.') } var wuzzle = new Wuzzle({log: log.child({component: 'wuzzle'})}); wuzzle.woos(); log.info('done with the wuzzle') node-bunyan-2.0.5/examples/unstringifyable.js000066400000000000000000000004711377616564400213430ustar00rootroot00000000000000// See how bunyan behaves with an un-stringify-able object. var bunyan = require('../lib/bunyan'); var log = bunyan.createLogger({src: true, name: 'foo'}); // Make a circular object (cannot be JSON-ified). var myobj = { foo: 'bar' }; myobj.myobj = myobj; log.info({obj: myobj}, 'hi there'); // <--- here node-bunyan-2.0.5/lib/000077500000000000000000000000001377616564400145265ustar00rootroot00000000000000node-bunyan-2.0.5/lib/bunyan.js000066400000000000000000001407201377616564400163640ustar00rootroot00000000000000/** * Copyright (c) 2017 Trent Mick. * Copyright (c) 2017 Joyent Inc. * * The bunyan logging library for node.js. * * -*- mode: js -*- * vim: expandtab:ts=4:sw=4 */ /* * Bunyan log format version. This becomes the 'v' field on all log records. * This will be incremented if there is any backward incompatible change to * the log record format. Details will be in 'CHANGES.md' (the change log). */ var LOG_VERSION = 0; var xxx = function xxx(s) { // internal dev/debug logging var args = ['XX' + 'X: '+s].concat( Array.prototype.slice.call(arguments, 1)); console.error.apply(this, args); }; var xxx = function xxx() {}; // comment out to turn on debug logging /* * Runtime environment notes: * * Bunyan is intended to run in a number of runtime environments. Here are * some notes on differences for those envs and how the code copes. * * - node.js: The primary target environment. * - NW.js: http://nwjs.io/ An *app* environment that feels like both a * node env -- it has node-like globals (`process`, `global`) and * browser-like globals (`window`, `navigator`). My *understanding* is that * bunyan can operate as if this is vanilla node.js. * - browser: Failing the above, we sniff using the `window` global * . * - browserify: http://browserify.org/ A browser-targetting bundler of * node.js deps. The runtime is a browser env, so can't use fs access, * etc. Browserify's build looks for `require()` imports * to bundle. For some imports it won't be able to handle, we "hide" * from browserify with `require('frobshizzle' + '')`. * - Other? Please open issues if things are broken. */ var runtimeEnv; if (typeof (process) !== 'undefined' && process.versions) { if (process.versions.nw) { runtimeEnv = 'nw'; } else if (process.versions.node) { runtimeEnv = 'node'; } } if (!runtimeEnv && typeof (window) !== 'undefined' && window.window === window) { runtimeEnv = 'browser'; } if (!runtimeEnv) { throw new Error('unknown runtime environment'); } var os, fs, dtrace; if (runtimeEnv === 'browser') { os = { hostname: function () { return window.location.host; } }; fs = {}; dtrace = null; } else { os = require('os'); fs = require('fs'); try { dtrace = require('dtrace-provider' + ''); } catch (e) { dtrace = null; } } var util = require('util'); var assert = require('assert'); var EventEmitter = require('events').EventEmitter; var stream = require('stream'); try { var safeJsonStringify = require('safe-json-stringify'); } catch (e) { safeJsonStringify = null; } if (process.env.BUNYAN_TEST_NO_SAFE_JSON_STRINGIFY) { safeJsonStringify = null; } // The 'mv' module is required for rotating-file stream support. try { var mv = require('mv' + ''); } catch (e) { mv = null; } try { var sourceMapSupport = require('source-map-support' + ''); } catch (_) { sourceMapSupport = null; } //---- Internal support stuff /** * A shallow copy of an object. Bunyan logging attempts to never cause * exceptions, so this function attempts to handle non-objects gracefully. */ function objCopy(obj) { if (obj == null) { // null or undefined return obj; } else if (Array.isArray(obj)) { return obj.slice(); } else if (typeof (obj) === 'object') { var copy = {}; Object.keys(obj).forEach(function (k) { copy[k] = obj[k]; }); return copy; } else { return obj; } } var format = util.format; if (!format) { // If node < 0.6, then use its `util.format`: // : var inspect = util.inspect; var formatRegExp = /%[sdj%]/g; format = function format(f) { if (typeof (f) !== 'string') { var objects = []; for (var i = 0; i < arguments.length; i++) { objects.push(inspect(arguments[i])); } return objects.join(' '); } var i = 1; var args = arguments; var len = args.length; var str = String(f).replace(formatRegExp, function (x) { if (i >= len) return x; switch (x) { case '%s': return String(args[i++]); case '%d': return Number(args[i++]); case '%j': return fastAndSafeJsonStringify(args[i++]); case '%%': return '%'; default: return x; } }); for (var x = args[i]; i < len; x = args[++i]) { if (x === null || typeof (x) !== 'object') { str += ' ' + x; } else { str += ' ' + inspect(x); } } return str; }; } /** * Gather some caller info 3 stack levels up. * See . */ function getCaller3Info() { if (this === undefined) { // Cannot access caller info in 'strict' mode. return; } var obj = {}; var saveLimit = Error.stackTraceLimit; var savePrepare = Error.prepareStackTrace; Error.stackTraceLimit = 3; Error.prepareStackTrace = function (_, stack) { var caller = stack[2]; if (sourceMapSupport) { caller = sourceMapSupport.wrapCallSite(caller); } obj.file = caller.getFileName(); obj.line = caller.getLineNumber(); var func = caller.getFunctionName(); if (func) obj.func = func; }; Error.captureStackTrace(this, getCaller3Info); this.stack; Error.stackTraceLimit = saveLimit; Error.prepareStackTrace = savePrepare; return obj; } function _indent(s, indent) { if (!indent) indent = ' '; var lines = s.split(/\r?\n/g); return indent + lines.join('\n' + indent); } /** * Warn about an bunyan processing error. * * @param msg {String} Message with which to warn. * @param dedupKey {String} Optional. A short string key for this warning to * have its warning only printed once. */ function _warn(msg, dedupKey) { assert.ok(msg); if (dedupKey) { if (_warned[dedupKey]) { return; } _warned[dedupKey] = true; } process.stderr.write(msg + '\n'); } function _haveWarned(dedupKey) { return _warned[dedupKey]; } var _warned = {}; function ConsoleRawStream() {} ConsoleRawStream.prototype.write = function (rec) { if (rec.level < INFO) { console.log(rec); } else if (rec.level < WARN) { console.info(rec); } else if (rec.level < ERROR) { console.warn(rec); } else { console.error(rec); } }; //---- Levels var TRACE = 10; var DEBUG = 20; var INFO = 30; var WARN = 40; var ERROR = 50; var FATAL = 60; var levelFromName = { 'trace': TRACE, 'debug': DEBUG, 'info': INFO, 'warn': WARN, 'error': ERROR, 'fatal': FATAL }; var nameFromLevel = {}; Object.keys(levelFromName).forEach(function (name) { nameFromLevel[levelFromName[name]] = name; }); // Dtrace probes. var dtp = undefined; var probes = dtrace && {}; /** * Resolve a level number, name (upper or lowercase) to a level number value. * * @param nameOrNum {String|Number} A level name (case-insensitive) or positive * integer level. * @api public */ function resolveLevel(nameOrNum) { var level; var type = typeof (nameOrNum); if (type === 'string') { level = levelFromName[nameOrNum.toLowerCase()]; if (!level) { throw new Error(format('unknown level name: "%s"', nameOrNum)); } } else if (type !== 'number') { throw new TypeError(format('cannot resolve level: invalid arg (%s):', type, nameOrNum)); } else if (nameOrNum < 0 || Math.floor(nameOrNum) !== nameOrNum) { throw new TypeError(format('level is not a positive integer: %s', nameOrNum)); } else { level = nameOrNum; } return level; } function isWritable(obj) { if (obj instanceof stream.Writable) { return true; } return typeof (obj.write) === 'function'; } //---- Logger class /** * Create a Logger instance. * * @param options {Object} See documentation for full details. At minimum * this must include a 'name' string key. Configuration keys: * - `streams`: specify the logger output streams. This is an array of * objects with these fields: * - `type`: The stream type. See README.md for full details. * Often this is implied by the other fields. Examples are * 'file', 'stream' and "raw". * - `level`: Defaults to 'info'. * - `path` or `stream`: The specify the file path or writeable * stream to which log records are written. E.g. * `stream: process.stdout`. * - `closeOnExit` (boolean): Optional. Default is true for a * 'file' stream when `path` is given, false otherwise. * See README.md for full details. * - `level`: set the level for a single output stream (cannot be used * with `streams`) * - `stream`: the output stream for a logger with just one, e.g. * `process.stdout` (cannot be used with `streams`) * - `serializers`: object mapping log record field names to * serializing functions. See README.md for details. * - `src`: Boolean (default false). Set true to enable 'src' automatic * field with log call source info. * All other keys are log record fields. * * An alternative *internal* call signature is used for creating a child: * new Logger(, [, ]); * * @param _childSimple (Boolean) An assertion that the given `_childOptions` * (a) only add fields (no config) and (b) no serialization handling is * required for them. IOW, this is a fast path for frequent child * creation. */ function Logger(options, _childOptions, _childSimple) { xxx('Logger start:', options) if (!(this instanceof Logger)) { return new Logger(options, _childOptions); } // Input arg validation. var parent; if (_childOptions !== undefined) { parent = options; options = _childOptions; if (!(parent instanceof Logger)) { throw new TypeError( 'invalid Logger creation: do not pass a second arg'); } } if (!options) { throw new TypeError('options (object) is required'); } if (!parent) { if (!options.name) { throw new TypeError('options.name (string) is required'); } } else { if (options.name) { throw new TypeError( 'invalid options.name: child cannot set logger name'); } } if (options.stream && options.streams) { throw new TypeError('cannot mix "streams" and "stream" options'); } if (options.streams && !Array.isArray(options.streams)) { throw new TypeError('invalid options.streams: must be an array') } if (options.serializers && (typeof (options.serializers) !== 'object' || Array.isArray(options.serializers))) { throw new TypeError('invalid options.serializers: must be an object') } EventEmitter.call(this); // Fast path for simple child creation. if (parent && _childSimple) { // `_isSimpleChild` is a signal to stream close handling that this child // owns none of its streams. this._isSimpleChild = true; this._level = parent._level; this.streams = parent.streams; this.serializers = parent.serializers; this.src = parent.src; var fields = this.fields = {}; var parentFieldNames = Object.keys(parent.fields); for (var i = 0; i < parentFieldNames.length; i++) { var name = parentFieldNames[i]; fields[name] = parent.fields[name]; } var names = Object.keys(options); for (var i = 0; i < names.length; i++) { var name = names[i]; fields[name] = options[name]; } return; } // Start values. var self = this; if (parent) { this._level = parent._level; this.streams = []; for (var i = 0; i < parent.streams.length; i++) { var s = objCopy(parent.streams[i]); s.closeOnExit = false; // Don't own parent stream. this.streams.push(s); } this.serializers = objCopy(parent.serializers); this.src = parent.src; this.fields = objCopy(parent.fields); if (options.level) { this.level(options.level); } } else { this._level = Number.POSITIVE_INFINITY; this.streams = []; this.serializers = null; this.src = false; this.fields = {}; } if (!dtp && dtrace) { dtp = dtrace.createDTraceProvider('bunyan'); for (var level in levelFromName) { var probe; probes[levelFromName[level]] = probe = dtp.addProbe('log-' + level, 'char *'); // Explicitly add a reference to dtp to prevent it from being GC'd probe.dtp = dtp; } dtp.enable(); } // Handle *config* options (i.e. options that are not just plain data // for log records). if (options.stream) { self.addStream({ type: 'stream', stream: options.stream, closeOnExit: false, level: options.level }); } else if (options.streams) { options.streams.forEach(function (s) { self.addStream(s, options.level); }); } else if (parent && options.level) { this.level(options.level); } else if (!parent) { if (runtimeEnv === 'browser') { /* * In the browser we'll be emitting to console.log by default. * Any console.log worth its salt these days can nicely render * and introspect objects (e.g. the Firefox and Chrome console) * so let's emit the raw log record. Are there browsers for which * that breaks things? */ self.addStream({ type: 'raw', stream: new ConsoleRawStream(), closeOnExit: false, level: options.level }); } else { self.addStream({ type: 'stream', stream: process.stdout, closeOnExit: false, level: options.level }); } } if (options.serializers) { self.addSerializers(options.serializers); } if (options.src) { this.src = true; } xxx('Logger: ', self) // Fields. // These are the default fields for log records (minus the attributes // removed in this constructor). To allow storing raw log records // (unrendered), `this.fields` must never be mutated. Create a copy for // any changes. var fields = objCopy(options); delete fields.stream; delete fields.level; delete fields.streams; delete fields.serializers; delete fields.src; if (this.serializers) { this._applySerializers(fields); } if (!fields.hostname && !self.fields.hostname) { fields.hostname = os.hostname(); } if (!fields.pid) { fields.pid = process.pid; } Object.keys(fields).forEach(function (k) { self.fields[k] = fields[k]; }); } util.inherits(Logger, EventEmitter); /** * Add a stream * * @param stream {Object}. Object with these fields: * - `type`: The stream type. See README.md for full details. * Often this is implied by the other fields. Examples are * 'file', 'stream' and "raw". * - `path` or `stream`: The specify the file path or writeable * stream to which log records are written. E.g. * `stream: process.stdout`. * - `level`: Optional. Falls back to `defaultLevel`. * - `closeOnExit` (boolean): Optional. Default is true for a * 'file' stream when `path` is given, false otherwise. * See README.md for full details. * @param defaultLevel {Number|String} Optional. A level to use if * `stream.level` is not set. If neither is given, this defaults to INFO. */ Logger.prototype.addStream = function addStream(s, defaultLevel) { var self = this; if (defaultLevel === null || defaultLevel === undefined) { defaultLevel = INFO; } s = objCopy(s); // Implicit 'type' from other args. if (!s.type) { if (s.stream) { s.type = 'stream'; } else if (s.path) { s.type = 'file' } } s.raw = (s.type === 'raw'); // PERF: Allow for faster check in `_emit`. if (s.level !== undefined) { s.level = resolveLevel(s.level); } else { s.level = resolveLevel(defaultLevel); } if (s.level < self._level) { self._level = s.level; } switch (s.type) { case 'stream': assert.ok(isWritable(s.stream), '"stream" stream is not writable: ' + util.inspect(s.stream)); if (!s.closeOnExit) { s.closeOnExit = false; } break; case 'file': if (s.reemitErrorEvents === undefined) { s.reemitErrorEvents = true; } if (!s.stream) { s.stream = fs.createWriteStream(s.path, {flags: 'a', encoding: 'utf8'}); if (!s.closeOnExit) { s.closeOnExit = true; } } else { if (!s.closeOnExit) { s.closeOnExit = false; } } break; case 'rotating-file': assert.ok(!s.stream, '"rotating-file" stream should not give a "stream"'); assert.ok(s.path); assert.ok(mv, '"rotating-file" stream type is not supported: ' + 'missing "mv" module'); s.stream = new RotatingFileStream(s); if (!s.closeOnExit) { s.closeOnExit = true; } break; case 'raw': if (!s.closeOnExit) { s.closeOnExit = false; } break; default: throw new TypeError('unknown stream type "' + s.type + '"'); } if (s.reemitErrorEvents && typeof (s.stream.on) === 'function') { // TODO: When we have `.close()`, it should remove event // listeners to not leak Logger instances. s.stream.on('error', function onStreamError(err) { self.emit('error', err, s); }); } self.streams.push(s); delete self.haveNonRawStreams; // reset } /** * Add serializers * * @param serializers {Object} Optional. Object mapping log record field names * to serializing functions. See README.md for details. */ Logger.prototype.addSerializers = function addSerializers(serializers) { var self = this; if (!self.serializers) { self.serializers = {}; } Object.keys(serializers).forEach(function (field) { var serializer = serializers[field]; if (typeof (serializer) !== 'function') { throw new TypeError(format( 'invalid serializer for "%s" field: must be a function', field)); } else { self.serializers[field] = serializer; } }); } /** * Create a child logger, typically to add a few log record fields. * * This can be useful when passing a logger to a sub-component, e.g. a * 'wuzzle' component of your service: * * var wuzzleLog = log.child({component: 'wuzzle'}) * var wuzzle = new Wuzzle({..., log: wuzzleLog}) * * Then log records from the wuzzle code will have the same structure as * the app log, *plus the component='wuzzle' field*. * * @param options {Object} Optional. Set of options to apply to the child. * All of the same options for a new Logger apply here. Notes: * - The parent's streams are inherited and cannot be removed in this * call. Any given `streams` are *added* to the set inherited from * the parent. * - The parent's serializers are inherited, though can effectively be * overwritten by using duplicate keys. * - Can use `level` to set the level of the streams inherited from * the parent. The level for the parent is NOT affected. * @param simple {Boolean} Optional. Set to true to assert that `options` * (a) only add fields (no config) and (b) no serialization handling is * required for them. IOW, this is a fast path for frequent child * creation. See 'tools/timechild.js' for numbers. */ Logger.prototype.child = function (options, simple) { return new (this.constructor)(this, options || {}, simple); } /** * A convenience method to reopen 'file' streams on a logger. This can be * useful with external log rotation utilities that move and re-open log files * (e.g. logrotate on Linux, logadm on SmartOS/Illumos). Those utilities * typically have rotation options to copy-and-truncate the log file, but * you may not want to use that. An alternative is to do this in your * application: * * var log = bunyan.createLogger(...); * ... * process.on('SIGUSR2', function () { * log.reopenFileStreams(); * }); * ... * * See . */ Logger.prototype.reopenFileStreams = function () { var self = this; self.streams.forEach(function (s) { if (s.type === 'file') { if (s.stream) { // Not sure if typically would want this, or more immediate // `s.stream.destroy()`. s.stream.end(); s.stream.destroySoon(); delete s.stream; } s.stream = fs.createWriteStream(s.path, {flags: 'a', encoding: 'utf8'}); s.stream.on('error', function (err) { self.emit('error', err, s); }); } }); }; /* BEGIN JSSTYLED */ /** * Close this logger. * * This closes streams (that it owns, as per 'endOnClose' attributes on * streams), etc. Typically you **don't** need to bother calling this. Logger.prototype.close = function () { if (this._closed) { return; } if (!this._isSimpleChild) { self.streams.forEach(function (s) { if (s.endOnClose) { xxx('closing stream s:', s); s.stream.end(); s.endOnClose = false; } }); } this._closed = true; } */ /* END JSSTYLED */ /** * Get/set the level of all streams on this logger. * * Get Usage: * // Returns the current log level (lowest level of all its streams). * log.level() -> INFO * * Set Usage: * log.level(INFO) // set all streams to level INFO * log.level('info') // can use 'info' et al aliases */ Logger.prototype.level = function level(value) { if (value === undefined) { return this._level; } var newLevel = resolveLevel(value); var len = this.streams.length; for (var i = 0; i < len; i++) { this.streams[i].level = newLevel; } this._level = newLevel; } /** * Get/set the level of a particular stream on this logger. * * Get Usage: * // Returns an array of the levels of each stream. * log.levels() -> [TRACE, INFO] * * // Returns a level of the identified stream. * log.levels(0) -> TRACE // level of stream at index 0 * log.levels('foo') // level of stream with name 'foo' * * Set Usage: * log.levels(0, INFO) // set level of stream 0 to INFO * log.levels(0, 'info') // can use 'info' et al aliases * log.levels('foo', WARN) // set stream named 'foo' to WARN * * Stream names: When streams are defined, they can optionally be given * a name. For example, * log = new Logger({ * streams: [ * { * name: 'foo', * path: '/var/log/my-service/foo.log' * level: 'trace' * }, * ... * * @param name {String|Number} The stream index or name. * @param value {Number|String} The level value (INFO) or alias ('info'). * If not given, this is a 'get' operation. * @throws {Error} If there is no stream with the given name. */ Logger.prototype.levels = function levels(name, value) { if (name === undefined) { assert.equal(value, undefined); return this.streams.map( function (s) { return s.level }); } var stream; if (typeof (name) === 'number') { stream = this.streams[name]; if (stream === undefined) { throw new Error('invalid stream index: ' + name); } } else { var len = this.streams.length; for (var i = 0; i < len; i++) { var s = this.streams[i]; if (s.name === name) { stream = s; break; } } if (!stream) { throw new Error(format('no stream with name "%s"', name)); } } if (value === undefined) { return stream.level; } else { var newLevel = resolveLevel(value); stream.level = newLevel; if (newLevel < this._level) { this._level = newLevel; } } } /** * Apply registered serializers to the appropriate keys in the given fields. * * Pre-condition: This is only called if there is at least one serializer. * * @param fields (Object) The log record fields. * @param excludeFields (Object) Optional mapping of keys to `true` for * keys to NOT apply a serializer. */ Logger.prototype._applySerializers = function (fields, excludeFields) { var self = this; xxx('_applySerializers: excludeFields', excludeFields); // Check each serializer against these (presuming number of serializers // is typically less than number of fields). Object.keys(this.serializers).forEach(function (name) { if (fields[name] === undefined || (excludeFields && excludeFields[name])) { return; } xxx('_applySerializers; apply to "%s" key', name) try { fields[name] = self.serializers[name](fields[name]); } catch (err) { _warn(format('bunyan: ERROR: Exception thrown from the "%s" ' + 'Bunyan serializer. This should never happen. This is a bug ' + 'in that serializer function.\n%s', name, err.stack || err)); fields[name] = format('(Error in Bunyan log "%s" serializer ' + 'broke field. See stderr for details.)', name); } }); } /** * Emit a log record. * * @param rec {log record} * @param noemit {Boolean} Optional. Set to true to skip emission * and just return the JSON string. */ Logger.prototype._emit = function (rec, noemit) { var i; // Lazily determine if this Logger has non-'raw' streams. If there are // any, then we need to stringify the log record. if (this.haveNonRawStreams === undefined) { this.haveNonRawStreams = false; for (i = 0; i < this.streams.length; i++) { if (!this.streams[i].raw) { this.haveNonRawStreams = true; break; } } } // Stringify the object (creates a warning str on error). var str; if (noemit || this.haveNonRawStreams) { str = fastAndSafeJsonStringify(rec) + os.EOL; } if (noemit) return str; var level = rec.level; for (i = 0; i < this.streams.length; i++) { var s = this.streams[i]; if (s.level <= level) { xxx('writing log rec "%s" to "%s" stream (%d <= %d): %j', rec.msg, s.type, s.level, level, rec); s.stream.write(s.raw ? rec : str); } }; return str; } /** * Build a record object suitable for emitting from the arguments * provided to the a log emitter. */ function mkRecord(log, minLevel, args) { var excludeFields, fields, msgArgs; if (args[0] instanceof Error) { // `log.(err, ...)` fields = { // Use this Logger's err serializer, if defined. err: (log.serializers && log.serializers.err ? log.serializers.err(args[0]) : Logger.stdSerializers.err(args[0])) }; excludeFields = {err: true}; if (args.length === 1) { msgArgs = [fields.err.message]; } else { msgArgs = args.slice(1); } } else if (typeof (args[0]) !== 'object' || Array.isArray(args[0])) { // `log.(msg, ...)` fields = null; msgArgs = args.slice(); } else if (Buffer.isBuffer(args[0])) { // `log.(buf, ...)` // Almost certainly an error, show `inspect(buf)`. See bunyan // issue #35. fields = null; msgArgs = args.slice(); msgArgs[0] = util.inspect(msgArgs[0]); } else { // `log.(fields, msg, ...)` fields = args[0]; if (fields && args.length === 1 && fields.err && fields.err instanceof Error) { msgArgs = [fields.err.message]; } else { msgArgs = args.slice(1); } } // Build up the record object. var rec = objCopy(log.fields); var level = rec.level = minLevel; var recFields = (fields ? objCopy(fields) : null); if (recFields) { if (log.serializers) { log._applySerializers(recFields, excludeFields); } Object.keys(recFields).forEach(function (k) { rec[k] = recFields[k]; }); } rec.msg = format.apply(log, msgArgs); if (!rec.time) { rec.time = (new Date()); } // Get call source info if (log.src && !rec.src) { rec.src = getCaller3Info() } rec.v = LOG_VERSION; return rec; }; /** * Build an array that dtrace-provider can use to fire a USDT probe. If we've * already built the appropriate string, we use it. Otherwise, build the * record object and stringify it. */ function mkProbeArgs(str, log, minLevel, msgArgs) { return [ str || log._emit(mkRecord(log, minLevel, msgArgs), true) ]; } /** * Build a log emitter function for level minLevel. I.e. this is the * creator of `log.info`, `log.error`, etc. */ function mkLogEmitter(minLevel) { return function () { var log = this; var str = null; var rec = null; if (!this._emit) { /* * Show this invalid Bunyan usage warning *once*. * * See for * an example of how this can happen. */ var dedupKey = 'unbound'; if (!_haveWarned[dedupKey]) { var caller = getCaller3Info(); _warn(format('bunyan usage error: %s:%s: attempt to log ' + 'with an unbound log method: `this` is: %s', caller.file, caller.line, util.inspect(this)), dedupKey); } return; } else if (arguments.length === 0) { // `log.()` return (this._level <= minLevel); } var msgArgs = new Array(arguments.length); for (var i = 0; i < msgArgs.length; ++i) { msgArgs[i] = arguments[i]; } if (this._level <= minLevel) { rec = mkRecord(log, minLevel, msgArgs); str = this._emit(rec); } if (probes) { probes[minLevel].fire(mkProbeArgs, str, log, minLevel, msgArgs); } } } /** * The functions below log a record at a specific level. * * Usages: * log.() -> boolean is-trace-enabled * log.( err, [ msg, ...]) * log.( msg, ...) * log.( fields, msg, ...) * * where is the lowercase version of the log level. E.g.: * * log.info() * * @params fields {Object} Optional set of additional fields to log. * @params msg {String} Log message. This can be followed by additional * arguments that are handled like * [util.format](http://nodejs.org/docs/latest/api/all.html#util.format). */ Logger.prototype.trace = mkLogEmitter(TRACE); Logger.prototype.debug = mkLogEmitter(DEBUG); Logger.prototype.info = mkLogEmitter(INFO); Logger.prototype.warn = mkLogEmitter(WARN); Logger.prototype.error = mkLogEmitter(ERROR); Logger.prototype.fatal = mkLogEmitter(FATAL); //---- Standard serializers // A serializer is a function that serializes a JavaScript object to a // JSON representation for logging. There is a standard set of presumed // interesting objects in node.js-land. Logger.stdSerializers = {}; // Serialize an HTTP request. Logger.stdSerializers.req = function (req) { if (!req || !req.connection) return req; return { method: req.method, // Accept `req.originalUrl` for expressjs usage. // https://expressjs.com/en/api.html#req.originalUrl url: req.originalUrl || req.url, headers: req.headers, remoteAddress: req.connection.remoteAddress, remotePort: req.connection.remotePort }; // Trailers: Skipping for speed. If you need trailers in your app, then // make a custom serializer. //if (Object.keys(trailers).length > 0) { // obj.trailers = req.trailers; //} }; // Serialize an HTTP response. Logger.stdSerializers.res = function (res) { if (!res || !res.statusCode) return res; return { statusCode: res.statusCode, header: res._header } }; /* * This function dumps long stack traces for exceptions having a cause() * method. The error classes from * [verror](https://github.com/davepacheco/node-verror) and * [restify v2.0](https://github.com/mcavage/node-restify) are examples. * * Based on `dumpException` in * https://github.com/davepacheco/node-extsprintf/blob/master/lib/extsprintf.js */ function getFullErrorStack(ex) { var ret = ex.stack || ex.toString(); if (ex.cause && typeof (ex.cause) === 'function') { var cex = ex.cause(); if (cex) { ret += '\nCaused by: ' + getFullErrorStack(cex); } } return (ret); } // Serialize an Error object // (Core error properties are enumerable in node 0.4, not in 0.6). var errSerializer = Logger.stdSerializers.err = function (err) { if (!err || !err.stack) return err; var obj = { message: err.message, name: err.name, stack: getFullErrorStack(err), code: err.code, signal: err.signal } return obj; }; // A JSON stringifier that handles cycles safely - tracks seen values in a Set. function safeCyclesSet() { var seen = new Set(); return function (key, val) { if (!val || typeof (val) !== 'object') { return val; } if (seen.has(val)) { return '[Circular]'; } seen.add(val); return val; }; } /** * A JSON stringifier that handles cycles safely - tracks seen vals in an Array. * * Note: This approach has performance problems when dealing with large objects, * see trentm/node-bunyan#445, but since this is the only option for node 0.10 * and earlier (as Set was introduced in Node 0.12), it's used as a fallback * when Set is not available. */ function safeCyclesArray() { var seen = []; return function (key, val) { if (!val || typeof (val) !== 'object') { return val; } if (seen.indexOf(val) !== -1) { return '[Circular]'; } seen.push(val); return val; }; } /** * A JSON stringifier that handles cycles safely. * * Usage: JSON.stringify(obj, safeCycles()) * * Choose the best safe cycle function from what is available - see * trentm/node-bunyan#445. */ var safeCycles = typeof (Set) !== 'undefined' ? safeCyclesSet : safeCyclesArray; /** * A fast JSON.stringify that handles cycles and getter exceptions (when * safeJsonStringify is installed). * * This function attempts to use the regular JSON.stringify for speed, but on * error (e.g. JSON cycle detection exception) it falls back to safe stringify * handlers that can deal with cycles and/or getter exceptions. */ function fastAndSafeJsonStringify(rec) { try { return JSON.stringify(rec); } catch (ex) { try { return JSON.stringify(rec, safeCycles()); } catch (e) { if (safeJsonStringify) { return safeJsonStringify(rec); } else { var dedupKey = e.stack.split(/\n/g, 3).join('\n'); _warn('bunyan: ERROR: Exception in ' + '`JSON.stringify(rec)`. You can install the ' + '"safe-json-stringify" module to have Bunyan fallback ' + 'to safer stringification. Record:\n' + _indent(format('%s\n%s', util.inspect(rec), e.stack)), dedupKey); return format('(Exception in JSON.stringify(rec): %j. ' + 'See stderr for details.)', e.message); } } } } var RotatingFileStream = null; if (mv) { RotatingFileStream = function RotatingFileStream(options) { this.path = options.path; this.count = (options.count == null ? 10 : options.count); assert.equal(typeof (this.count), 'number', format('rotating-file stream "count" is not a number: %j (%s) in %j', this.count, typeof (this.count), this)); assert.ok(this.count >= 0, format('rotating-file stream "count" is not >= 0: %j in %j', this.count, this)); // Parse `options.period`. if (options.period) { // where scope is: // h hours (at the start of the hour) // d days (at the start of the day, i.e. just after midnight) // w weeks (at the start of Sunday) // m months (on the first of the month) // y years (at the start of Jan 1st) // with special values 'hourly' (1h), 'daily' (1d), "weekly" (1w), // 'monthly' (1m) and 'yearly' (1y) var period = { 'hourly': '1h', 'daily': '1d', 'weekly': '1w', 'monthly': '1m', 'yearly': '1y' }[options.period] || options.period; var m = /^([1-9][0-9]*)([hdwmy]|ms)$/.exec(period); if (!m) { throw new Error(format('invalid period: "%s"', options.period)); } this.periodNum = Number(m[1]); this.periodScope = m[2]; } else { this.periodNum = 1; this.periodScope = 'd'; } var lastModified = null; try { var fileInfo = fs.statSync(this.path); lastModified = fileInfo.mtime.getTime(); } catch (err) { // file doesn't exist } var rotateAfterOpen = false; if (lastModified) { var lastRotTime = this._calcRotTime(0); if (lastModified < lastRotTime) { rotateAfterOpen = true; } } // TODO: template support for backup files // template: // default is %P.%n // '/var/log/archive/foo.log' -> foo.log.%n // '/var/log/archive/foo.log.%n' // codes: // XXX support strftime codes (per node version of those) // or whatever module. Pick non-colliding for extra // codes // %P `path` base value // %n integer number of rotated log (1,2,3,...) // %d datetime in YYYY-MM-DD_HH-MM-SS // XXX what should default date format be? // prior art? Want to avoid ':' in // filenames (illegal on Windows for one). this.stream = fs.createWriteStream(this.path, {flags: 'a', encoding: 'utf8'}); this.rotQueue = []; this.rotating = false; if (rotateAfterOpen) { this._debug('rotateAfterOpen -> call rotate()'); this.rotate(); } else { this._setupNextRot(); } } util.inherits(RotatingFileStream, EventEmitter); RotatingFileStream.prototype._debug = function () { // Set this to `true` to add debug logging. if (false) { if (arguments.length === 0) { return true; } var args = Array.prototype.slice.call(arguments); args[0] = '[' + (new Date().toISOString()) + ', ' + this.path + '] ' + args[0]; console.log.apply(this, args); } else { return false; } }; RotatingFileStream.prototype._setupNextRot = function () { this.rotAt = this._calcRotTime(1); this._setRotationTimer(); } RotatingFileStream.prototype._setRotationTimer = function () { var self = this; var delay = this.rotAt - Date.now(); // Cap timeout to Node's max setTimeout, see // . var TIMEOUT_MAX = 2147483647; // 2^31-1 if (delay > TIMEOUT_MAX) { delay = TIMEOUT_MAX; } this.timeout = setTimeout( function () { self._debug('_setRotationTimer timeout -> call rotate()'); self.rotate(); }, delay); if (typeof (this.timeout.unref) === 'function') { this.timeout.unref(); } } RotatingFileStream.prototype._calcRotTime = function _calcRotTime(periodOffset) { this._debug('_calcRotTime: %s%s', this.periodNum, this.periodScope); var d = new Date(); this._debug(' now local: %s', d); this._debug(' now utc: %s', d.toISOString()); var rotAt; switch (this.periodScope) { case 'ms': // Hidden millisecond period for debugging. if (this.rotAt) { rotAt = this.rotAt + this.periodNum * periodOffset; } else { rotAt = Date.now() + this.periodNum * periodOffset; } break; case 'h': if (this.rotAt) { rotAt = this.rotAt + this.periodNum * 60 * 60 * 1000 * periodOffset; } else { // First time: top of the next hour. rotAt = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours() + periodOffset); } break; case 'd': if (this.rotAt) { rotAt = this.rotAt + this.periodNum * 24 * 60 * 60 * 1000 * periodOffset; } else { // First time: start of tomorrow (i.e. at the coming midnight) UTC. rotAt = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() + periodOffset); } break; case 'w': // Currently, always on Sunday morning at 00:00:00 (UTC). if (this.rotAt) { rotAt = this.rotAt + this.periodNum * 7 * 24 * 60 * 60 * 1000 * periodOffset; } else { // First time: this coming Sunday. var dayOffset = (7 - d.getUTCDay()); if (periodOffset < 1) { dayOffset = -d.getUTCDay(); } if (periodOffset > 1 || periodOffset < -1) { dayOffset += 7 * periodOffset; } rotAt = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() + dayOffset); } break; case 'm': if (this.rotAt) { rotAt = Date.UTC(d.getUTCFullYear(), d.getUTCMonth() + this.periodNum * periodOffset, 1); } else { // First time: the start of the next month. rotAt = Date.UTC(d.getUTCFullYear(), d.getUTCMonth() + periodOffset, 1); } break; case 'y': if (this.rotAt) { rotAt = Date.UTC(d.getUTCFullYear() + this.periodNum * periodOffset, 0, 1); } else { // First time: the start of the next year. rotAt = Date.UTC(d.getUTCFullYear() + periodOffset, 0, 1); } break; default: assert.fail(format('invalid period scope: "%s"', this.periodScope)); } if (this._debug()) { this._debug(' **rotAt**: %s (utc: %s)', rotAt, new Date(rotAt).toUTCString()); var now = Date.now(); this._debug(' now: %s (%sms == %smin == %sh to go)', now, rotAt - now, (rotAt-now)/1000/60, (rotAt-now)/1000/60/60); } return rotAt; }; RotatingFileStream.prototype.rotate = function rotate() { // XXX What about shutdown? var self = this; // If rotation period is > ~25 days, we have to break into multiple // setTimeout's. See . if (self.rotAt && self.rotAt > Date.now()) { return self._setRotationTimer(); } this._debug('rotate'); if (self.rotating) { throw new TypeError('cannot start a rotation when already rotating'); } self.rotating = true; self.stream.end(); // XXX can do moves sync after this? test at high rate function del() { var toDel = self.path + '.' + String(n - 1); if (n === 0) { toDel = self.path; } n -= 1; self._debug(' rm %s', toDel); fs.unlink(toDel, function (delErr) { //XXX handle err other than not exists moves(); }); } function moves() { if (self.count === 0 || n < 0) { return finish(); } var before = self.path; var after = self.path + '.' + String(n); if (n > 0) { before += '.' + String(n - 1); } n -= 1; fs.exists(before, function (exists) { if (!exists) { moves(); } else { self._debug(' mv %s %s', before, after); mv(before, after, function (mvErr) { if (mvErr) { self.emit('error', mvErr); finish(); // XXX finish here? } else { moves(); } }); } }) } function finish() { self._debug(' open %s', self.path); self.stream = fs.createWriteStream(self.path, {flags: 'a', encoding: 'utf8'}); var q = self.rotQueue, len = q.length; for (var i = 0; i < len; i++) { self.stream.write(q[i]); } self.rotQueue = []; self.rotating = false; self.emit('drain'); self._setupNextRot(); } var n = this.count; del(); }; RotatingFileStream.prototype.write = function write(s) { if (this.rotating) { this.rotQueue.push(s); return false; } else { return this.stream.write(s); } }; RotatingFileStream.prototype.end = function end(s) { this.stream.end(); }; RotatingFileStream.prototype.destroy = function destroy(s) { this.stream.destroy(); }; RotatingFileStream.prototype.destroySoon = function destroySoon(s) { this.stream.destroySoon(); }; } /* if (mv) */ /** * RingBuffer is a Writable Stream that just stores the last N records in * memory. * * @param options {Object}, with the following fields: * * - limit: number of records to keep in memory */ function RingBuffer(options) { this.limit = options && options.limit ? options.limit : 100; this.writable = true; this.records = []; EventEmitter.call(this); } util.inherits(RingBuffer, EventEmitter); RingBuffer.prototype.write = function (record) { if (!this.writable) throw (new Error('RingBuffer has been ended already')); this.records.push(record); if (this.records.length > this.limit) this.records.shift(); return (true); }; RingBuffer.prototype.end = function () { if (arguments.length > 0) this.write.apply(this, Array.prototype.slice.call(arguments)); this.writable = false; }; RingBuffer.prototype.destroy = function () { this.writable = false; this.emit('close'); }; RingBuffer.prototype.destroySoon = function () { this.destroy(); }; //---- Exports module.exports = Logger; module.exports.TRACE = TRACE; module.exports.DEBUG = DEBUG; module.exports.INFO = INFO; module.exports.WARN = WARN; module.exports.ERROR = ERROR; module.exports.FATAL = FATAL; module.exports.resolveLevel = resolveLevel; module.exports.levelFromName = levelFromName; module.exports.nameFromLevel = nameFromLevel; module.exports.VERSION = require('../package.json').version; module.exports.LOG_VERSION = LOG_VERSION; module.exports.createLogger = function createLogger(options) { return new Logger(options); }; module.exports.RingBuffer = RingBuffer; module.exports.RotatingFileStream = RotatingFileStream; // Useful for custom `type == 'raw'` streams that may do JSON stringification // of log records themselves. Usage: // var str = JSON.stringify(rec, bunyan.safeCycles()); module.exports.safeCycles = safeCycles; node-bunyan-2.0.5/package-lock.json000066400000000000000000003276761377616564400172210ustar00rootroot00000000000000{ "name": "bunyan", "version": "2.0.5", "lockfileVersion": 1, "requires": true, "dependencies": { "ansi-red": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", "integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=", "dev": true, "requires": { "ansi-wrap": "0.1.0" } }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", "dev": true }, "ansi-wrap": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", "dev": true }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { "sprintf-js": "~1.0.2" } }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", "dev": true, "requires": { "safer-buffer": "~2.1.0" } }, "assert-plus": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", "dev": true }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, "autolinker": { "version": "0.28.1", "resolved": "https://registry.npmjs.org/autolinker/-/autolinker-0.28.1.tgz", "integrity": "sha1-BlK0kYgYefB3XazgzcoyM5QqTkc=", "dev": true, "requires": { "gulp-header": "^1.7.1" } }, "aws-sign2": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", "dev": true }, "aws4": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==", "dev": true }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "dev": true, "requires": { "tweetnacl": "^0.14.3" } }, "ben": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/ben/-/ben-0.0.0.tgz", "integrity": "sha1-xrX63l/7SuCeogQXDPEjTCWE5gQ=", "dev": true }, "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true }, "boom": { "version": "2.10.1", "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", "dev": true, "requires": { "hoek": "2.x.x" } }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, "caseless": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=", "dev": true }, "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", "has-ansi": "^2.0.0", "strip-ansi": "^3.0.0", "supports-color": "^2.0.0" } }, "clean-yaml-object": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/clean-yaml-object/-/clean-yaml-object-0.1.0.tgz", "integrity": "sha1-Y/sRDcLOGoTcIfbZM0h20BCui2g=", "dev": true }, "coffee-script": { "version": "1.12.7", "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz", "integrity": "sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==", "dev": true }, "color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", "dev": true }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, "requires": { "delayed-stream": "~1.0.0" } }, "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "dev": true, "requires": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^2.2.2", "typedarray": "^0.0.6" } }, "concat-with-sourcemaps": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", "dev": true, "requires": { "source-map": "^0.6.1" } }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, "coveralls": { "version": "2.13.3", "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-2.13.3.tgz", "integrity": "sha512-iiAmn+l1XqRwNLXhW8Rs5qHZRFMYp9ZIPjEOVRpC/c4so6Y/f4/lFi0FfR5B9cCqgyhkJ5cZmbvcVRfP8MHchw==", "dev": true, "requires": { "js-yaml": "3.6.1", "lcov-parse": "0.0.10", "log-driver": "1.2.5", "minimist": "1.2.0", "request": "2.79.0" }, "dependencies": { "esprima": { "version": "2.7.3", "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", "dev": true }, "js-yaml": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.6.1.tgz", "integrity": "sha1-bl/mfYsgXOTSL60Ft3geja3MSzA=", "dev": true, "requires": { "argparse": "^1.0.7", "esprima": "^2.6.0" } }, "minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } } }, "cross-spawn": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", "dev": true, "requires": { "lru-cache": "^4.0.1", "which": "^1.2.9" } }, "cryptiles": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", "dev": true, "requires": { "boom": "2.x.x" } }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "dev": true, "requires": { "assert-plus": "^1.0.0" }, "dependencies": { "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true } } }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { "ms": "2.0.0" } }, "deeper": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/deeper/-/deeper-2.1.0.tgz", "integrity": "sha1-vFZOX3MXT98gHgiwADDooU2nQ2g=", "dev": true }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, "diff": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", "dev": true }, "dtrace-provider": { "version": "0.8.8", "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz", "integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==", "optional": true, "requires": { "nan": "^2.14.0" } }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "dev": true, "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" } }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, "events-to-array": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/events-to-array/-/events-to-array-1.1.2.tgz", "integrity": "sha1-LUH1Y+H+QA7Uli/hpNXGp1Od9/Y=", "dev": true }, "exeunt": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/exeunt/-/exeunt-1.1.0.tgz", "integrity": "sha1-r3Lbb5Szy3XpIa7jddUTBJhD0oQ=" }, "expand-range": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", "dev": true, "requires": { "fill-range": "^2.1.0" } }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, "extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { "is-extendable": "^0.1.0" } }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", "dev": true }, "fill-range": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", "dev": true, "requires": { "is-number": "^2.1.0", "isobject": "^2.0.0", "randomatic": "^3.0.0", "repeat-element": "^1.1.2", "repeat-string": "^1.5.2" } }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, "foreground-child": { "version": "1.5.6", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz", "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=", "dev": true, "requires": { "cross-spawn": "^4", "signal-exit": "^3.0.0" } }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", "dev": true }, "form-data": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.5", "mime-types": "^2.1.12" } }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, "generate-function": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", "dev": true, "requires": { "is-property": "^1.0.2" } }, "generate-object-property": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", "dev": true, "requires": { "is-property": "^1.0.0" } }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "dev": true, "requires": { "assert-plus": "^1.0.0" }, "dependencies": { "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true } } }, "glob": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", "optional": true, "requires": { "inflight": "^1.0.4", "inherits": "2", "minimatch": "2 || 3", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "gray-matter": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-2.1.1.tgz", "integrity": "sha1-MELZrewqHe1qdwep7SOA+KF6Qw4=", "dev": true, "requires": { "ansi-red": "^0.1.1", "coffee-script": "^1.12.4", "extend-shallow": "^2.0.1", "js-yaml": "^3.8.1", "toml": "^2.3.2" } }, "gulp-header": { "version": "1.8.12", "resolved": "https://registry.npmjs.org/gulp-header/-/gulp-header-1.8.12.tgz", "integrity": "sha512-lh9HLdb53sC7XIZOYzTXM4lFuXElv3EVkSDhsd7DoJBj7hm+Ni7D3qYbb+Rr8DuM8nRanBvkVO9d7askreXGnQ==", "dev": true, "requires": { "concat-with-sourcemaps": "*", "lodash.template": "^4.4.0", "through2": "^2.0.0" } }, "har-validator": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", "dev": true, "requires": { "chalk": "^1.1.1", "commander": "^2.9.0", "is-my-json-valid": "^2.12.4", "pinkie-promise": "^2.0.0" } }, "has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "dev": true, "requires": { "ansi-regex": "^2.0.0" } }, "hawk": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", "dev": true, "requires": { "boom": "2.x.x", "cryptiles": "2.x.x", "hoek": "2.x.x", "sntp": "1.x.x" } }, "hoek": { "version": "2.16.3", "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", "dev": true }, "http-signature": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", "dev": true, "requires": { "assert-plus": "^0.2.0", "jsprim": "^1.2.2", "sshpk": "^1.7.0" } }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { "once": "^1.3.0", "wrappy": "1" } }, "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", "dev": true }, "is-my-ip-valid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", "dev": true }, "is-my-json-valid": { "version": "2.20.1", "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.20.1.tgz", "integrity": "sha512-KWo8x7CYK5goqyYFJM4ZmapN2DvIGKkx5C1WLHyo2Dcr4R9u4Y9ofpZxn5+LF/d0FHR3AeFcYxVlqIA2OOwuJA==", "dev": true, "requires": { "generate-function": "^2.0.0", "generate-object-property": "^1.1.0", "is-my-ip-valid": "^1.0.0", "jsonpointer": "^4.0.0", "xtend": "^4.0.0" } }, "is-number": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", "dev": true, "requires": { "kind-of": "^3.0.2" } }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "requires": { "isobject": "^3.0.1" }, "dependencies": { "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", "dev": true } } }, "is-property": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", "dev": true }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, "isexe": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/isexe/-/isexe-1.1.2.tgz", "integrity": "sha1-NvPiLmB1CSD15yQaR2qMakInWtA=", "dev": true }, "isobject": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", "dev": true, "requires": { "isarray": "1.0.0" } }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", "dev": true }, "js-yaml": { "version": "3.14.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", "dev": true, "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" } }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", "dev": true }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", "dev": true }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", "dev": true }, "jsonpointer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", "dev": true }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", "dev": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", "json-schema": "0.2.3", "verror": "1.10.0" }, "dependencies": { "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "dev": true, "requires": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } } } }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { "is-buffer": "^1.1.5" } }, "lazy-cache": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-2.0.2.tgz", "integrity": "sha1-uRkKT5EzVGlIQIWfio9whNiCImQ=", "dev": true, "requires": { "set-getter": "^0.1.0" } }, "lcov-parse": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-0.0.10.tgz", "integrity": "sha1-GwuP+ayceIklBYK3C3ExXZ2m2aM=", "dev": true }, "list-item": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/list-item/-/list-item-1.1.1.tgz", "integrity": "sha1-DGXQDih8tmPMs8s4Sad+iewmilY=", "dev": true, "requires": { "expand-range": "^1.8.1", "extend-shallow": "^2.0.1", "is-number": "^2.1.0", "repeat-string": "^1.5.2" } }, "lodash._reinterpolate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", "dev": true }, "lodash.template": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", "dev": true, "requires": { "lodash._reinterpolate": "^3.0.0", "lodash.templatesettings": "^4.0.0" } }, "lodash.templatesettings": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", "dev": true, "requires": { "lodash._reinterpolate": "^3.0.0" } }, "log-driver": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.5.tgz", "integrity": "sha1-euTsJXMC/XkNVXyxDJcQDYV7AFY=", "dev": true }, "lru-cache": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "dev": true, "requires": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" } }, "markdown-link": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/markdown-link/-/markdown-link-0.1.1.tgz", "integrity": "sha1-MsXGUZmmRXMWMi0eQinRNAfIx88=", "dev": true }, "markdown-toc": { "version": "0.12.16", "resolved": "https://registry.npmjs.org/markdown-toc/-/markdown-toc-0.12.16.tgz", "integrity": "sha1-jxT0uUswx4XzZAITzOoMMAH3n7M=", "dev": true, "requires": { "concat-stream": "^1.5.1", "gray-matter": "^2.0.2", "lazy-cache": "^2.0.1", "list-item": "^1.1.1", "markdown-link": "^0.1.1", "minimist": "^1.2.0", "mixin-deep": "^1.1.3", "object.pick": "^1.1.2", "remarkable": "^1.6.2", "repeat-string": "^1.5.4", "strip-color": "^0.1.0" } }, "math-random": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", "dev": true }, "mime-db": { "version": "1.44.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", "dev": true }, "mime-types": { "version": "2.1.27", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", "dev": true, "requires": { "mime-db": "1.44.0" } }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "mixin-deep": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", "dev": true, "requires": { "for-in": "^1.0.2", "is-extendable": "^1.0.1" }, "dependencies": { "is-extendable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { "is-plain-object": "^2.0.4" } } } }, "mkdirp": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", "optional": true, "requires": { "minimist": "^1.2.5" } }, "moment": { "version": "2.27.0", "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz", "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==", "optional": true }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, "mv": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", "optional": true, "requires": { "mkdirp": "~0.5.1", "ncp": "~2.0.0", "rimraf": "~2.4.0" } }, "nan": { "version": "2.14.1", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", "optional": true }, "ncp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", "optional": true }, "nyc": { "version": "10.3.2", "resolved": "https://registry.npmjs.org/nyc/-/nyc-10.3.2.tgz", "integrity": "sha1-8n9NkfKp2zbCT1dP9cbv/wIz3kY=", "dev": true, "requires": { "archy": "^1.0.0", "arrify": "^1.0.1", "caching-transform": "^1.0.0", "convert-source-map": "^1.3.0", "debug-log": "^1.0.1", "default-require-extensions": "^1.0.0", "find-cache-dir": "^0.1.1", "find-up": "^1.1.2", "foreground-child": "^1.5.3", "glob": "^7.0.6", "istanbul-lib-coverage": "^1.1.0", "istanbul-lib-hook": "^1.0.6", "istanbul-lib-instrument": "^1.7.1", "istanbul-lib-report": "^1.1.0", "istanbul-lib-source-maps": "^1.2.0", "istanbul-reports": "^1.1.0", "md5-hex": "^1.2.0", "merge-source-map": "^1.0.2", "micromatch": "^2.3.11", "mkdirp": "^0.5.0", "resolve-from": "^2.0.0", "rimraf": "^2.5.4", "signal-exit": "^3.0.1", "spawn-wrap": "1.2.4", "test-exclude": "^4.1.0", "yargs": "^7.1.0", "yargs-parser": "^5.0.0" }, "dependencies": { "align-text": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", "dev": true, "optional": true, "requires": { "kind-of": "^3.0.2", "longest": "^1.0.1", "repeat-string": "^1.5.2" } }, "amdefine": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", "dev": true }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", "dev": true }, "append-transform": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-0.4.0.tgz", "integrity": "sha1-126/jKlNJ24keja61EpLdKthGZE=", "dev": true, "requires": { "default-require-extensions": "^1.0.0" } }, "archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", "dev": true }, "arr-diff": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", "dev": true, "requires": { "arr-flatten": "^1.0.1" } }, "arr-flatten": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.0.3.tgz", "integrity": "sha1-onTthawIhJtr14R8RYB0XcUa37E=", "dev": true }, "array-unique": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", "dev": true }, "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, "async": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", "dev": true }, "babel-code-frame": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz", "integrity": "sha1-AnYgvuVnqIwyVhV05/0IAdMxGOQ=", "dev": true, "requires": { "chalk": "^1.1.0", "esutils": "^2.0.2", "js-tokens": "^3.0.0" } }, "babel-generator": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.24.1.tgz", "integrity": "sha1-5xX0hsWN7SVknYiJRNUqoHxdlJc=", "dev": true, "requires": { "babel-messages": "^6.23.0", "babel-runtime": "^6.22.0", "babel-types": "^6.24.1", "detect-indent": "^4.0.0", "jsesc": "^1.3.0", "lodash": "^4.2.0", "source-map": "^0.5.0", "trim-right": "^1.0.1" } }, "babel-messages": { "version": "6.23.0", "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", "dev": true, "requires": { "babel-runtime": "^6.22.0" } }, "babel-runtime": { "version": "6.23.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", "integrity": "sha1-CpSJ8UTecO+zzkMArM2zKeL8VDs=", "dev": true, "requires": { "core-js": "^2.4.0", "regenerator-runtime": "^0.10.0" } }, "babel-template": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.24.1.tgz", "integrity": "sha1-BK5RTx+Ts6JTfyoPYKWkX7gwgzM=", "dev": true, "requires": { "babel-runtime": "^6.22.0", "babel-traverse": "^6.24.1", "babel-types": "^6.24.1", "babylon": "^6.11.0", "lodash": "^4.2.0" } }, "babel-traverse": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.24.1.tgz", "integrity": "sha1-qzZnP9NW+aCUhlnnszjV/q2zFpU=", "dev": true, "requires": { "babel-code-frame": "^6.22.0", "babel-messages": "^6.23.0", "babel-runtime": "^6.22.0", "babel-types": "^6.24.1", "babylon": "^6.15.0", "debug": "^2.2.0", "globals": "^9.0.0", "invariant": "^2.2.0", "lodash": "^4.2.0" } }, "babel-types": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.24.1.tgz", "integrity": "sha1-oTaHncFbNga9oNkMH8dDBML/CXU=", "dev": true, "requires": { "babel-runtime": "^6.22.0", "esutils": "^2.0.2", "lodash": "^4.2.0", "to-fast-properties": "^1.0.1" } }, "babylon": { "version": "6.17.0", "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.17.0.tgz", "integrity": "sha1-N9qUiHhIi5xOPEA4iT+jMUs/yTI=", "dev": true }, "balanced-match": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", "dev": true }, "brace-expansion": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz", "integrity": "sha1-Pv/DxQ4ABTH7cg6v+A8K6O8jz1k=", "dev": true, "requires": { "balanced-match": "^0.4.1", "concat-map": "0.0.1" } }, "braces": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", "dev": true, "requires": { "expand-range": "^1.8.1", "preserve": "^0.2.0", "repeat-element": "^1.1.2" } }, "builtin-modules": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", "dev": true }, "caching-transform": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-1.0.1.tgz", "integrity": "sha1-bb2y8g+Nj7znnz6U6dF0Lc31wKE=", "dev": true, "requires": { "md5-hex": "^1.2.0", "mkdirp": "^0.5.1", "write-file-atomic": "^1.1.4" } }, "camelcase": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", "dev": true, "optional": true }, "center-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", "dev": true, "optional": true, "requires": { "align-text": "^0.1.3", "lazy-cache": "^1.0.3" } }, "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", "has-ansi": "^2.0.0", "strip-ansi": "^3.0.0", "supports-color": "^2.0.0" } }, "cliui": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", "dev": true, "optional": true, "requires": { "center-align": "^0.1.1", "right-align": "^0.1.1", "wordwrap": "0.0.2" }, "dependencies": { "wordwrap": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", "dev": true, "optional": true } } }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true }, "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, "convert-source-map": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.0.tgz", "integrity": "sha1-ms1whRxtXf3ZPZKC5e35SgP/RrU=", "dev": true }, "core-js": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz", "integrity": "sha1-TekR5mew6ukSTjQlS1OupvxhjT4=", "dev": true }, "cross-spawn": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", "dev": true, "requires": { "lru-cache": "^4.0.1", "which": "^1.2.9" } }, "debug": { "version": "2.6.6", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.6.tgz", "integrity": "sha1-qfpvvpykPPHnn3O3XAGJy7fW21o=", "dev": true, "requires": { "ms": "0.7.3" } }, "debug-log": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/debug-log/-/debug-log-1.0.1.tgz", "integrity": "sha1-IwdjLUwEOCuN+KMvcLiVBG1SdF8=", "dev": true }, "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, "default-require-extensions": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz", "integrity": "sha1-836hXT4T/9m0N9M+GnW1+5eHTLg=", "dev": true, "requires": { "strip-bom": "^2.0.0" } }, "detect-indent": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", "dev": true, "requires": { "repeating": "^2.0.0" } }, "error-ex": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", "dev": true, "requires": { "is-arrayish": "^0.2.1" } }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, "esutils": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", "dev": true }, "expand-brackets": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", "dev": true, "requires": { "is-posix-bracket": "^0.1.0" } }, "expand-range": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", "dev": true, "requires": { "fill-range": "^2.1.0" } }, "extglob": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", "dev": true, "requires": { "is-extglob": "^1.0.0" } }, "filename-regex": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", "dev": true }, "fill-range": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", "dev": true, "requires": { "is-number": "^2.1.0", "isobject": "^2.0.0", "randomatic": "^1.1.3", "repeat-element": "^1.1.2", "repeat-string": "^1.5.2" } }, "find-cache-dir": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-0.1.1.tgz", "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk=", "dev": true, "requires": { "commondir": "^1.0.1", "mkdirp": "^0.5.1", "pkg-dir": "^1.0.0" } }, "find-up": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "dev": true, "requires": { "path-exists": "^2.0.0", "pinkie-promise": "^2.0.0" } }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, "for-own": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", "dev": true, "requires": { "for-in": "^1.0.1" } }, "foreground-child": { "version": "1.5.6", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz", "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=", "dev": true, "requires": { "cross-spawn": "^4", "signal-exit": "^3.0.0" } }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, "get-caller-file": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", "dev": true }, "glob": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.2", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "glob-base": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", "dev": true, "requires": { "glob-parent": "^2.0.0", "is-glob": "^2.0.0" } }, "glob-parent": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", "dev": true, "requires": { "is-glob": "^2.0.0" } }, "globals": { "version": "9.17.0", "resolved": "https://registry.npmjs.org/globals/-/globals-9.17.0.tgz", "integrity": "sha1-DAymltm5u2lNLlRwvTd3fKrVAoY=", "dev": true }, "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", "dev": true }, "handlebars": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.8.tgz", "integrity": "sha1-Irh1zT8ObL6jAxTxROgrx6cv9CA=", "dev": true, "requires": { "async": "^1.4.0", "optimist": "^0.6.1", "source-map": "^0.4.4", "uglify-js": "^2.6" }, "dependencies": { "source-map": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", "dev": true, "requires": { "amdefine": ">=0.0.4" } } } }, "has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "dev": true, "requires": { "ansi-regex": "^2.0.0" } }, "has-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", "dev": true }, "hosted-git-info": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.4.2.tgz", "integrity": "sha1-AHa59GonBQbduq6lZJaJdGBhKmc=", "dev": true }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" } }, "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, "invariant": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz", "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=", "dev": true, "requires": { "loose-envify": "^1.0.0" } }, "invert-kv": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", "dev": true }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, "is-buffer": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=", "dev": true }, "is-builtin-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "dev": true, "requires": { "builtin-modules": "^1.0.0" } }, "is-dotfile": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.2.tgz", "integrity": "sha1-LBMjg/ORmfjtwmjKAbmwB9IFzE0=", "dev": true }, "is-equal-shallow": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", "dev": true, "requires": { "is-primitive": "^2.0.0" } }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", "dev": true }, "is-extglob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", "dev": true }, "is-finite": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", "dev": true, "requires": { "number-is-nan": "^1.0.0" } }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { "number-is-nan": "^1.0.0" } }, "is-glob": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, "requires": { "is-extglob": "^1.0.0" } }, "is-number": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", "dev": true, "requires": { "kind-of": "^3.0.2" } }, "is-posix-bracket": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", "dev": true }, "is-primitive": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", "dev": true }, "is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", "dev": true }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, "isobject": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", "dev": true, "requires": { "isarray": "1.0.0" } }, "istanbul-lib-coverage": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.0.tgz", "integrity": "sha1-ysoZ3srvNSW11jMdcB8/O3rUhSg=", "dev": true }, "istanbul-lib-hook": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.0.6.tgz", "integrity": "sha1-wIZtHoHPLVMZJJUQEx/Bbe5JIx8=", "dev": true, "requires": { "append-transform": "^0.4.0" } }, "istanbul-lib-instrument": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.7.1.tgz", "integrity": "sha1-Fp4xvGLHeIUamUOd2Zw8wSGE02A=", "dev": true, "requires": { "babel-generator": "^6.18.0", "babel-template": "^6.16.0", "babel-traverse": "^6.18.0", "babel-types": "^6.18.0", "babylon": "^6.13.0", "istanbul-lib-coverage": "^1.1.0", "semver": "^5.3.0" } }, "istanbul-lib-report": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.0.tgz", "integrity": "sha1-RExOzKmvqTz1hPVrEPGVv3aMB3A=", "dev": true, "requires": { "istanbul-lib-coverage": "^1.1.0", "mkdirp": "^0.5.1", "path-parse": "^1.0.5", "supports-color": "^3.1.2" }, "dependencies": { "supports-color": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", "dev": true, "requires": { "has-flag": "^1.0.0" } } } }, "istanbul-lib-source-maps": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.0.tgz", "integrity": "sha1-jHcG1Jfib+62rz4MKP1bBmlZjQ4=", "dev": true, "requires": { "debug": "^2.6.3", "istanbul-lib-coverage": "^1.1.0", "mkdirp": "^0.5.1", "rimraf": "^2.6.1", "source-map": "^0.5.3" } }, "istanbul-reports": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.1.0.tgz", "integrity": "sha1-HvO3lYiSGc+1+tFjZfbOEI1fjGY=", "dev": true, "requires": { "handlebars": "^4.0.3" } }, "js-tokens": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.1.tgz", "integrity": "sha1-COnxMkhKLEWjCQfp3E1VZ7fxFNc=", "dev": true }, "jsesc": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", "dev": true }, "kind-of": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.0.tgz", "integrity": "sha1-tYq+TVwEStM3JqjBUltIz4kb/wc=", "dev": true, "requires": { "is-buffer": "^1.1.5" } }, "lazy-cache": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", "dev": true, "optional": true }, "lcid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", "dev": true, "requires": { "invert-kv": "^1.0.0" } }, "load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { "graceful-fs": "^4.1.2", "parse-json": "^2.2.0", "pify": "^2.0.0", "pinkie-promise": "^2.0.0", "strip-bom": "^2.0.0" } }, "lodash": { "version": "4.17.4", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", "dev": true }, "longest": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", "dev": true, "optional": true }, "loose-envify": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", "dev": true, "requires": { "js-tokens": "^3.0.0" } }, "lru-cache": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=", "dev": true, "requires": { "pseudomap": "^1.0.1", "yallist": "^2.0.0" } }, "md5-hex": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-1.3.0.tgz", "integrity": "sha1-0sSv6YPENwZiF5uMrRRSGRNQRsQ=", "dev": true, "requires": { "md5-o-matic": "^0.1.1" } }, "md5-o-matic": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/md5-o-matic/-/md5-o-matic-0.1.1.tgz", "integrity": "sha1-givM1l4RfFFPqxdrJZRdVBAKA8M=", "dev": true }, "merge-source-map": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.0.3.tgz", "integrity": "sha1-2hQV8nIqURnbB7FMT5c0EIY6Kr8=", "dev": true, "requires": { "source-map": "^0.5.3" } }, "micromatch": { "version": "2.3.11", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", "dev": true, "requires": { "arr-diff": "^2.0.0", "array-unique": "^0.2.1", "braces": "^1.8.2", "expand-brackets": "^0.1.4", "extglob": "^0.3.1", "filename-regex": "^2.0.0", "is-extglob": "^1.0.0", "is-glob": "^2.0.1", "kind-of": "^3.0.2", "normalize-path": "^2.0.1", "object.omit": "^2.0.0", "parse-glob": "^3.0.4", "regex-cache": "^0.4.2" } }, "minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz", "integrity": "sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q=", "dev": true, "requires": { "brace-expansion": "^1.0.0" } }, "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { "minimist": "0.0.8" } }, "ms": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.3.tgz", "integrity": "sha1-cIFVpeROM/X9D8U+gdDUCpG+H/8=", "dev": true }, "normalize-package-data": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.8.tgz", "integrity": "sha1-2Bntoqne29H/pWPqQHHZNngilbs=", "dev": true, "requires": { "hosted-git-info": "^2.1.4", "is-builtin-module": "^1.0.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" } }, "normalize-path": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, "requires": { "remove-trailing-separator": "^1.0.1" } }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, "object.omit": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", "dev": true, "requires": { "for-own": "^0.1.4", "is-extendable": "^0.1.1" } }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { "wrappy": "1" } }, "optimist": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", "dev": true, "requires": { "minimist": "~0.0.1", "wordwrap": "~0.0.2" } }, "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true }, "os-locale": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "dev": true, "requires": { "lcid": "^1.0.0" } }, "parse-glob": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", "dev": true, "requires": { "glob-base": "^0.3.0", "is-dotfile": "^1.0.0", "is-extglob": "^1.0.0", "is-glob": "^2.0.0" } }, "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "requires": { "error-ex": "^1.2.0" } }, "path-exists": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "dev": true, "requires": { "pinkie-promise": "^2.0.0" } }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, "path-parse": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", "dev": true }, "path-type": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", "dev": true, "requires": { "graceful-fs": "^4.1.2", "pify": "^2.0.0", "pinkie-promise": "^2.0.0" } }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true }, "pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", "dev": true }, "pinkie-promise": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "dev": true, "requires": { "pinkie": "^2.0.0" } }, "pkg-dir": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", "dev": true, "requires": { "find-up": "^1.0.0" } }, "preserve": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", "dev": true }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", "dev": true }, "randomatic": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.6.tgz", "integrity": "sha1-EQ3Kv/OX6dz/fAeJzMCkmt8exbs=", "dev": true, "requires": { "is-number": "^2.0.2", "kind-of": "^3.0.2" } }, "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", "dev": true, "requires": { "load-json-file": "^1.0.0", "normalize-package-data": "^2.3.2", "path-type": "^1.0.0" } }, "read-pkg-up": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", "dev": true, "requires": { "find-up": "^1.0.0", "read-pkg": "^1.0.0" } }, "regenerator-runtime": { "version": "0.10.5", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", "dev": true }, "regex-cache": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.3.tgz", "integrity": "sha1-mxpsNdTQ3871cRrmUejp09cRQUU=", "dev": true, "requires": { "is-equal-shallow": "^0.1.3", "is-primitive": "^2.0.0" } }, "remove-trailing-separator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.0.1.tgz", "integrity": "sha1-YV67lq9VlVLUv0BXyENtSGq2PMQ=", "dev": true }, "repeat-element": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", "dev": true }, "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true }, "repeating": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "dev": true, "requires": { "is-finite": "^1.0.0" } }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, "require-main-filename": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", "dev": true }, "resolve-from": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=", "dev": true }, "right-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", "dev": true, "optional": true, "requires": { "align-text": "^0.1.1" } }, "rimraf": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", "dev": true, "requires": { "glob": "^7.0.5" } }, "semver": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", "dev": true }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true }, "slide": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=", "dev": true }, "source-map": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", "dev": true }, "spawn-wrap": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.2.4.tgz", "integrity": "sha1-kg6yEadpwJPuv71bDnpdLmirLkA=", "dev": true, "requires": { "foreground-child": "^1.3.3", "mkdirp": "^0.5.0", "os-homedir": "^1.0.1", "rimraf": "^2.3.3", "signal-exit": "^2.0.0", "which": "^1.2.4" }, "dependencies": { "signal-exit": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-2.1.2.tgz", "integrity": "sha1-N1h5sfkuvDszRIDQONxUam1VhWQ=", "dev": true } } }, "spdx-correct": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", "dev": true, "requires": { "spdx-license-ids": "^1.0.2" } }, "spdx-expression-parse": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=", "dev": true }, "spdx-license-ids": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=", "dev": true }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", "strip-ansi": "^3.0.0" } }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { "ansi-regex": "^2.0.0" } }, "strip-bom": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, "requires": { "is-utf8": "^0.2.0" } }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true }, "test-exclude": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-4.1.0.tgz", "integrity": "sha1-BMpwtzkN04yY1KADoXOAbKeZHJE=", "dev": true, "requires": { "arrify": "^1.0.1", "micromatch": "^2.3.11", "object-assign": "^4.1.0", "read-pkg-up": "^1.0.1", "require-main-filename": "^1.0.1" } }, "to-fast-properties": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", "dev": true }, "trim-right": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", "dev": true }, "uglify-js": { "version": "2.8.22", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.22.tgz", "integrity": "sha1-1Uk0d4qNoUkD+imjJvskwKtRoaA=", "dev": true, "optional": true, "requires": { "source-map": "~0.5.1", "uglify-to-browserify": "~1.0.0", "yargs": "~3.10.0" }, "dependencies": { "yargs": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", "dev": true, "optional": true, "requires": { "camelcase": "^1.0.2", "cliui": "^2.1.0", "decamelize": "^1.0.0", "window-size": "0.1.0" } } } }, "uglify-to-browserify": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", "dev": true, "optional": true }, "validate-npm-package-license": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", "dev": true, "requires": { "spdx-correct": "~1.0.0", "spdx-expression-parse": "~1.0.0" } }, "which": { "version": "1.2.14", "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", "dev": true, "requires": { "isexe": "^2.0.0" } }, "which-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", "dev": true }, "window-size": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", "dev": true, "optional": true }, "wordwrap": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", "dev": true }, "wrap-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { "string-width": "^1.0.1", "strip-ansi": "^3.0.1" } }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, "write-file-atomic": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz", "integrity": "sha1-+Aek8LHZ6ROuekgRLmzDrxmRtF8=", "dev": true, "requires": { "graceful-fs": "^4.1.11", "imurmurhash": "^0.1.4", "slide": "^1.1.5" } }, "y18n": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", "dev": true }, "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", "dev": true }, "yargs": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", "dev": true, "requires": { "camelcase": "^3.0.0", "cliui": "^3.2.0", "decamelize": "^1.1.1", "get-caller-file": "^1.0.1", "os-locale": "^1.4.0", "read-pkg-up": "^1.0.1", "require-directory": "^2.1.1", "require-main-filename": "^1.0.1", "set-blocking": "^2.0.0", "string-width": "^1.0.2", "which-module": "^1.0.0", "y18n": "^3.2.1", "yargs-parser": "^5.0.0" }, "dependencies": { "camelcase": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", "dev": true }, "cliui": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", "dev": true, "requires": { "string-width": "^1.0.1", "strip-ansi": "^3.0.1", "wrap-ansi": "^2.0.0" } } } }, "yargs-parser": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", "dev": true, "requires": { "camelcase": "^3.0.0" }, "dependencies": { "camelcase": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", "dev": true } } } } }, "oauth-sign": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", "dev": true }, "object.pick": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", "dev": true, "requires": { "isobject": "^3.0.1" }, "dependencies": { "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", "dev": true } } }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { "wrappy": "1" } }, "only-shallow": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/only-shallow/-/only-shallow-1.2.0.tgz", "integrity": "sha1-cc7O26kyS8BRiu8Q7AgNMkncJGU=", "dev": true }, "opener": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz", "integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==", "dev": true }, "os-homedir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.1.tgz", "integrity": "sha1-DWK99EuRb9O73PLKsZGUj7CU8Ac=", "dev": true }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", "dev": true }, "pinkie-promise": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "dev": true, "requires": { "pinkie": "^2.0.0" } }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", "dev": true }, "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", "dev": true }, "qs": { "version": "6.3.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.3.2.tgz", "integrity": "sha1-51vV9uJoEioqDgvaYwslUMFmUCw=", "dev": true }, "randomatic": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", "dev": true, "requires": { "is-number": "^4.0.0", "kind-of": "^6.0.0", "math-random": "^1.0.1" }, "dependencies": { "is-number": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", "dev": true }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true } } }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "remarkable": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/remarkable/-/remarkable-1.7.4.tgz", "integrity": "sha512-e6NKUXgX95whv7IgddywbeN/ItCkWbISmc2DiqHJb0wTrqZIexqdco5b8Z3XZoo/48IdNVKM9ZCvTPJ4F5uvhg==", "dev": true, "requires": { "argparse": "^1.0.10", "autolinker": "~0.28.0" } }, "repeat-element": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", "dev": true }, "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", "dev": true }, "request": { "version": "2.79.0", "resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz", "integrity": "sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4=", "dev": true, "requires": { "aws-sign2": "~0.6.0", "aws4": "^1.2.1", "caseless": "~0.11.0", "combined-stream": "~1.0.5", "extend": "~3.0.0", "forever-agent": "~0.6.1", "form-data": "~2.1.1", "har-validator": "~2.0.6", "hawk": "~3.1.3", "http-signature": "~1.1.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.7", "oauth-sign": "~0.8.1", "qs": "~6.3.0", "stringstream": "~0.0.4", "tough-cookie": "~2.3.0", "tunnel-agent": "~0.4.1", "uuid": "^3.0.0" } }, "rimraf": { "version": "2.4.5", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", "optional": true, "requires": { "glob": "^6.0.1" } }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, "safe-json-stringify": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", "optional": true }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, "set-getter": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/set-getter/-/set-getter-0.1.0.tgz", "integrity": "sha1-12nBgsnVpR9AkUXy+6guXoboA3Y=", "dev": true, "requires": { "to-object-path": "^0.3.0" } }, "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, "sntp": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", "dev": true, "requires": { "hoek": "2.x.x" } }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, "sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", "dev": true, "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", "bcrypt-pbkdf": "^1.0.0", "dashdash": "^1.12.0", "ecc-jsbn": "~0.1.1", "getpass": "^0.1.1", "jsbn": "~0.1.0", "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" }, "dependencies": { "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true } } }, "stack-utils": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-0.4.0.tgz", "integrity": "sha1-lAy4L8z6hOj/Lz/fKT/ngBa+zNE=", "dev": true }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" } }, "stringstream": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==", "dev": true }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { "ansi-regex": "^2.0.0" } }, "strip-color": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/strip-color/-/strip-color-0.1.0.tgz", "integrity": "sha1-EG9l09PmotlAHKwOsM6LinArT3s=", "dev": true }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true }, "tap": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/tap/-/tap-9.0.3.tgz", "integrity": "sha1-vT3uy1B/TjaEhu66jxDNePQvcoM=", "dev": true, "requires": { "bluebird": "^3.3.1", "clean-yaml-object": "^0.1.0", "color-support": "^1.1.0", "coveralls": "^2.11.2", "deeper": "^2.1.0", "foreground-child": "^1.3.3", "glob": "^7.0.0", "isexe": "^1.0.0", "js-yaml": "^3.3.1", "nyc": "^10.0.0", "only-shallow": "^1.0.2", "opener": "^1.4.1", "os-homedir": "1.0.1", "readable-stream": "^2.0.2", "signal-exit": "^3.0.0", "stack-utils": "^0.4.0", "tap-mocha-reporter": "^3.0.1", "tap-parser": "^4.2.2", "tmatch": "^3.0.0" }, "dependencies": { "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } } } }, "tap-mocha-reporter": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/tap-mocha-reporter/-/tap-mocha-reporter-3.0.9.tgz", "integrity": "sha512-VO07vhC9EG27EZdOe7bWBj1ldbK+DL9TnRadOgdQmiQOVZjFpUEQuuqO7+rNSO2kfmkq5hWeluYXDWNG/ytXTQ==", "dev": true, "requires": { "color-support": "^1.1.0", "debug": "^2.1.3", "diff": "^1.3.2", "escape-string-regexp": "^1.0.3", "glob": "^7.0.5", "js-yaml": "^3.3.1", "readable-stream": "^2.1.5", "tap-parser": "^5.1.0", "unicode-length": "^1.0.0" }, "dependencies": { "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "tap-parser": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/tap-parser/-/tap-parser-5.4.0.tgz", "integrity": "sha512-BIsIaGqv7uTQgTW1KLTMNPSEQf4zDDPgYOBRdgOfuB+JFOLRBfEu6cLa/KvMvmqggu1FKXDfitjLwsq4827RvA==", "dev": true, "requires": { "events-to-array": "^1.0.1", "js-yaml": "^3.2.7", "readable-stream": "^2" } } } }, "tap-parser": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/tap-parser/-/tap-parser-4.2.4.tgz", "integrity": "sha1-ZaKmAuSeF9uD+h8pX1P60DAlzE0=", "dev": true, "requires": { "events-to-array": "^1.0.1", "js-yaml": "^3.2.7", "readable-stream": "^2" } }, "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, "requires": { "readable-stream": "~2.3.6", "xtend": "~4.0.1" } }, "tmatch": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/tmatch/-/tmatch-3.1.0.tgz", "integrity": "sha512-W3MSATOCN4pVu2qFxmJLIArSifeSOFqnfx9hiUaVgOmeRoI2NbU7RNga+6G+L8ojlFeQge+ZPCclWyUpQ8UeNQ==", "dev": true }, "to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", "dev": true, "requires": { "kind-of": "^3.0.2" } }, "toml": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/toml/-/toml-2.3.6.tgz", "integrity": "sha512-gVweAectJU3ebq//Ferr2JUY4WKSDe5N+z0FvjDncLGyHmIDoxgY/2Ie4qfEIDm4IS7OA6Rmdm7pdEEdMcV/xQ==", "dev": true }, "tough-cookie": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", "dev": true, "requires": { "punycode": "^1.4.1" } }, "tunnel-agent": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", "dev": true }, "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "dev": true }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, "unicode-length": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/unicode-length/-/unicode-length-1.0.3.tgz", "integrity": "sha1-Wtp6f+1RhBpBijKM8UlHisg1irs=", "dev": true, "requires": { "punycode": "^1.3.2", "strip-ansi": "^3.0.1" } }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, "uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", "dev": true }, "vasync": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/vasync/-/vasync-1.4.3.tgz", "integrity": "sha1-yG1S4rcWE9Ke7fFZ8xNdvnSc7jc=", "dev": true, "requires": { "jsprim": "0.3.0", "verror": "1.1.0" }, "dependencies": { "extsprintf": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.0.tgz", "integrity": "sha1-TVi4Fazlvr/E6/A8+YsKdgSpm4Y=", "dev": true }, "json-schema": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.2.tgz", "integrity": "sha1-UDVPGfYDkXxpX3C4Wvp3w7DyNQY=", "dev": true }, "jsprim": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-0.3.0.tgz", "integrity": "sha1-zRNGbqJIDb2DlqVw1H0x3aR2+LE=", "dev": true, "requires": { "extsprintf": "1.0.0", "json-schema": "0.2.2", "verror": "1.3.3" }, "dependencies": { "verror": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.3.tgz", "integrity": "sha1-impKw6jHdLb2h/7OSb3/14VS4s0=", "dev": true, "requires": { "extsprintf": "1.0.0" } } } }, "verror": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.1.0.tgz", "integrity": "sha1-KktOsUogcFHnWm+U7lExW/FzobA=", "dev": true, "requires": { "extsprintf": "1.0.0" } } } }, "verror": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.3.tgz", "integrity": "sha1-impKw6jHdLb2h/7OSb3/14VS4s0=", "dev": true, "requires": { "extsprintf": "1.0.0" }, "dependencies": { "extsprintf": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.0.tgz", "integrity": "sha1-TVi4Fazlvr/E6/A8+YsKdgSpm4Y=", "dev": true } } }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "requires": { "isexe": "^2.0.0" }, "dependencies": { "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true } } }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "dev": true }, "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", "dev": true } } } node-bunyan-2.0.5/package.json000066400000000000000000000021251377616564400162460ustar00rootroot00000000000000{ "name": "bunyan", "version": "2.0.5", "description": "a JSON logging library for node.js services", "author": "Trent Mick (http://trentm.com)", "main": "./lib/bunyan.js", "bin": { "bunyan": "./bin/bunyan" }, "repository": { "type": "git", "url": "git://github.com/trentm/node-bunyan.git" }, "engines": [ "node >=0.10.0" ], "keywords": [ "log", "logging", "log4j", "json", "bunyan" ], "license": "MIT", "files": [ "bin", "lib" ], "dependencies": { "exeunt": "1.1.0" }, "// dtrace-provider": "required for dtrace features", "// mv": "required for RotatingFileStream", "// moment": "required for local time with CLI", "optionalDependencies": { "dtrace-provider": "~0.8", "mv": "~2", "safe-json-stringify": "~1", "moment": "^2.19.3" }, "devDependencies": { "ben": "0.0.0", "markdown-toc": "0.12.x", "tap": "^9.0.3", "vasync": "1.4.3", "verror": "1.3.3" }, "scripts": { "check": "make check", "test": "tap test/*.test.js # skip dtrace tests" } } node-bunyan-2.0.5/test/000077500000000000000000000000001377616564400147375ustar00rootroot00000000000000node-bunyan-2.0.5/test/add-stream.test.js000066400000000000000000000014031377616564400202720ustar00rootroot00000000000000/* * Copyright (c) 2016 Trent Mick. All rights reserved. * * Test stream adding. */ var test = require('tap').test; var bunyan = require('../lib/bunyan'); test('non-writables passed as stream', function (t) { var things = ['process.stdout', {}]; things.forEach(function (thing) { function createLogger() { bunyan.createLogger({ name: 'foo', stream: thing }); } t.throws(createLogger, /stream is not writable/, '"stream" stream is not writable'); }) t.end(); }); test('proper stream', function (t) { var log = bunyan.createLogger({ name: 'foo', stream: process.stdout }); t.ok('should not throw'); t.end(); }); node-bunyan-2.0.5/test/buffer.test.js000066400000000000000000000036221377616564400175270ustar00rootroot00000000000000/* * Copyright 2020 Trent Mick * Copyright 2012 Joyent Inc. * * Test logging with (accidental) usage of buffers. */ var util = require('util'), inspect = util.inspect, format = util.format; var test = require('tap').test; var bunyan = require('../lib/bunyan'); function Catcher() { this.records = []; } Catcher.prototype.write = function (record) { this.records.push(record); } var catcher = new Catcher(); var log = new bunyan.createLogger({ name: 'buffer.test', streams: [ { type: 'raw', stream: catcher, level: 'trace' } ] }); test('log.info(BUFFER)', function (t) { var b = Buffer.from ? Buffer.from('foo') : new Buffer('foo'); ['trace', 'debug', 'info', 'warn', 'error', 'fatal'].forEach(function (lvl) { log[lvl].call(log, b); var rec = catcher.records[catcher.records.length - 1]; t.equal(rec.msg, inspect(b), format('log.%s msg is inspect(BUFFER)', lvl)); t.ok(rec['0'] === undefined, 'no "0" array index key in record: ' + inspect(rec['0'])); t.ok(rec['parent'] === undefined, 'no "parent" array index key in record: ' + inspect(rec['parent'])); log[lvl].call(log, b, 'bar'); var rec = catcher.records[catcher.records.length - 1]; t.equal(rec.msg, inspect(b) + ' bar', format( 'log.%s(BUFFER, "bar") msg is inspect(BUFFER) + " bar"', lvl)); }); t.end(); }); //test('log.info({buf: BUFFER})', function (t) { // var b = Buffer.from ? Buffer.from('foo') : new Buffer('foo'); // // // Really there isn't much Bunyan can do here. See // // . An unwelcome hack would // // be to monkey-patch in Buffer.toJSON. Bletch. // log.info({buf: b}, 'my message'); // var rec = catcher.records[catcher.records.length - 1]; // // t.end(); //}); node-bunyan-2.0.5/test/child-behaviour.test.js000066400000000000000000000070521377616564400213240ustar00rootroot00000000000000/* * Copyright 2020 Trent Mick * * Test some `.child(...)` behaviour. */ var test = require('tap').test; var bunyan = require('../lib/bunyan'); function CapturingStream(recs) { this.recs = recs || []; } CapturingStream.prototype.write = function (rec) { this.recs.push(rec); } test('child can add stream', function (t) { var dadStream = new CapturingStream(); var dad = bunyan.createLogger({ name: 'surname', streams: [ { type: 'raw', stream: dadStream, level: 'info' } ] }); var sonStream = new CapturingStream(); var son = dad.child({ component: 'son', streams: [ { type: 'raw', stream: sonStream, level: 'debug' } ] }); dad.info('info from dad'); dad.debug('debug from dad'); son.debug('debug from son'); var rec; t.equal(dadStream.recs.length, 1); rec = dadStream.recs[0]; t.equal(rec.msg, 'info from dad'); t.equal(sonStream.recs.length, 1); rec = sonStream.recs[0]; t.equal(rec.msg, 'debug from son'); t.end(); }); test('child can set level of inherited streams', function (t) { var dadStream = new CapturingStream(); var dad = bunyan.createLogger({ name: 'surname', streams: [ { type: 'raw', stream: dadStream, level: 'info' } ] }); // Intention here is that the inherited `dadStream` logs at 'debug' level // for the son. var son = dad.child({ component: 'son', level: 'debug' }); dad.info('info from dad'); dad.debug('debug from dad'); son.debug('debug from son'); var rec; t.equal(dadStream.recs.length, 2); rec = dadStream.recs[0]; t.equal(rec.msg, 'info from dad'); rec = dadStream.recs[1]; t.equal(rec.msg, 'debug from son'); t.end(); }); test('child can set level of inherited streams and add streams', function (t) { var dadStream = new CapturingStream(); var dad = bunyan.createLogger({ name: 'surname', streams: [ { type: 'raw', stream: dadStream, level: 'info' } ] }); // Intention here is that the inherited `dadStream` logs at 'debug' level // for the son. var sonStream = new CapturingStream(); var son = dad.child({ component: 'son', level: 'trace', streams: [ { type: 'raw', stream: sonStream, level: 'debug' } ] }); dad.info('info from dad'); dad.trace('trace from dad'); son.trace('trace from son'); son.debug('debug from son'); t.equal(dadStream.recs.length, 3); t.equal(dadStream.recs[0].msg, 'info from dad'); t.equal(dadStream.recs[1].msg, 'trace from son'); t.equal(dadStream.recs[2].msg, 'debug from son'); t.equal(sonStream.recs.length, 1); t.equal(sonStream.recs[0].msg, 'debug from son'); t.end(); }); // issue #291 test('child should not lose parent "hostname"', function (t) { var stream = new CapturingStream(); var dad = bunyan.createLogger({ name: 'hostname-test', hostname: 'bar0', streams: [ { type: 'raw', stream: stream, level: 'info' } ] }); var son = dad.child({component: 'son'}); dad.info('HI'); son.info('hi'); t.equal(stream.recs.length, 2); t.equal(stream.recs[0].hostname, 'bar0'); t.equal(stream.recs[1].hostname, 'bar0'); t.equal(stream.recs[1].component, 'son'); t.end(); }); node-bunyan-2.0.5/test/cli-client-req.test.js000066400000000000000000000040401377616564400210610ustar00rootroot00000000000000/* * Copyright 2020 Trent Mick * * Test the bunyan CLI's handling of the "client_req" field. * "client_req" is a common-ish Bunyan log field from restify-clients. See: * // JSSTYLED * https://github.com/restify/clients/blob/85374f87db9f4469de2605b6b15632b317cc12be/lib/helpers/bunyan.js#L213 */ var exec = require('child_process').exec; var fs = require('fs'); var os = require('os'); var path = require('path'); var _ = require('util').format; var test = require('tap').test; // ---- globals var BUNYAN = path.resolve(__dirname, '../bin/bunyan'); if (os.platform() === 'win32') { BUNYAN = process.execPath + ' ' + BUNYAN; } // ---- tests test('client_req extra newlines, client_res={} (pull #252)', function (t) { var expect = [ /* BEGIN JSSTYLED */ '[2016-02-10T07:28:40.510Z] TRACE: aclientreq/23280 on danger0.local: request sent', ' GET /--ping HTTP/1.1', '[2016-02-10T07:28:41.419Z] TRACE: aclientreq/23280 on danger0.local: Response received', ' HTTP/1.1 200 OK', ' request-id: e8a5a700-cfc7-11e5-a3dc-3b85d20f26ef', ' content-type: application/json' /* END JSSTYLED */ ].join('\n') + '\n'; exec(_('%s %s/corpus/clientreqres.log', BUNYAN, __dirname), function (err, stdout, stderr) { t.ifError(err); t.equal(stdout, expect); t.end(); }); }); test('client_req.address is not used for Host header in 2.x (issue #504)', function (t) { exec(_('%s %s/corpus/client-req-with-address.log', BUNYAN, __dirname), function (err, stdout, stderr) { t.ifError(err) t.equal(stdout, [ // JSSTYLED '[2017-05-12T23:59:15.877Z] TRACE: minfo/66266 on sharptooth.local: request sent (client_req.address=127.0.0.1)', ' HEAD /dap/stor HTTP/1.1', ' accept: application/json, */*', ' host: foo.example.com', ' date: Fri, 12 May 2017 23:59:15 GMT', '' ].join('\n')); t.end(); }); }); node-bunyan-2.0.5/test/cli-res.test.js000066400000000000000000000030071377616564400176110ustar00rootroot00000000000000/* * Copyright 2020 Trent Mick * * Test the bunyan CLI's handling of the "res" field. */ var exec = require('child_process').exec; var fs = require('fs'); var os = require('os'); var path = require('path'); var _ = require('util').format; var test = require('tap').test; // ---- globals var BUNYAN = path.resolve(__dirname, '../bin/bunyan'); if (os.platform() === 'win32') { BUNYAN = process.execPath + ' ' + BUNYAN; } // ---- tests test('res with "header" string (issue #444)', function (t) { var expect = [ /* BEGIN JSSTYLED */ '[2017-08-02T22:37:34.798Z] INFO: res-header/76488 on danger0.local: response sent', ' HTTP/1.1 200 OK', ' Foo: bar', ' Date: Wed, 02 Aug 2017 22:37:34 GMT', ' Connection: keep-alive', ' Content-Length: 21' /* END JSSTYLED */ ].join('\n') + '\n'; exec(_('%s %s/corpus/res-header.log', BUNYAN, __dirname), function (err, stdout, stderr) { t.ifError(err); t.equal(stdout, expect); t.end(); }); }); test('res without "header"', function (t) { var expect = [ /* BEGIN JSSTYLED */ '[2017-08-02T22:37:34.798Z] INFO: res-header/76488 on danger0.local: response sent', ' HTTP/1.1 200 OK' /* END JSSTYLED */ ].join('\n') + '\n'; exec(_('%s %s/corpus/res-without-header.log', BUNYAN, __dirname), function (err, stdout, stderr) { t.ifError(err); t.equal(stdout, expect); t.end(); }); }); node-bunyan-2.0.5/test/cli.test.js000066400000000000000000000464171377616564400170360ustar00rootroot00000000000000/* * Copyright 2020 Trent Mick * * Test the `bunyan` CLI. */ var p = console.warn; var exec = require('child_process').exec; var fs = require('fs'); var os = require('os'); var path = require('path'); var _ = require('util').format; var test = require('tap').test; var vasync = require('vasync'); // ---- globals var BUNYAN = path.resolve(__dirname, '../bin/bunyan'); if (os.platform() === 'win32') { BUNYAN = process.execPath + ' ' + BUNYAN; } // ---- support stuff /** * Copies over all keys in `from` to `to`, or * to a new object if `to` is not given. */ function objCopy(from, to) { if (to === undefined) { to = {}; } for (var k in from) { to[k] = from[k]; } return to; } // ---- tests test('--version', function (t) { var version = require('../package.json').version; exec(BUNYAN + ' --version', function (err, stdout, stderr) { t.ifError(err) t.equal(stdout, 'bunyan ' + version + '\n'); t.end(); }); }); test('--help', function (t) { exec(BUNYAN + ' --help', function (err, stdout, stderr) { t.ifError(err) t.ok(stdout.indexOf('General options:') !== -1); t.end(); }); }); test('-h', function (t) { exec(BUNYAN + ' -h', function (err, stdout, stderr) { t.ifError(err) t.ok(stdout.indexOf('General options:') !== -1); t.end(); }); }); test('--bogus', function (t) { exec(BUNYAN + ' --bogus', function (err, stdout, stderr) { t.ok(err, 'should error out') t.equal(err.code, 1, '... with exit code 1') t.end(); }); }); test('simple.log', function (t) { exec(_('%s %s/corpus/simple.log', BUNYAN, __dirname), function (err, stdout, stderr) { t.ifError(err) t.equal(stdout, '[2012-02-08T22:56:52.856Z] INFO: myservice/123 on example.com: ' + 'My message\n'); t.end(); }); }); test('cat simple.log', function (t) { exec(_('cat %s/corpus/simple.log | %s', __dirname, BUNYAN), function (err, stdout, stderr) { t.ifError(err) t.equal(stdout, /* JSSTYLED */ '[2012-02-08T22:56:52.856Z] INFO: myservice/123 on example.com: My message\n'); t.end(); } ); }); // Test some local/UTC time handling by changing to a known non-UTC timezone // for some tests. // // I don't know how to effectively do this on Windows (at least // https://stackoverflow.com/questions/2611017 concurs), so we skip these on // Windows. Help is welcome if you know how to do this on Windows. test('time TZ tests', { skip: os.platform() === 'win32' ? 'do not know how to set timezone on Windows' : false }, function (suite) { // A stable 'TZ' for 'local' timezone output. tzEnv = objCopy(process.env); tzEnv.TZ = 'Pacific/Honolulu'; test('time: simple.log local long', function (t) { exec(_('%s -o long -L %s/corpus/simple.log', BUNYAN, __dirname), {env: tzEnv}, function (err, stdout, stderr) { t.ifError(err) t.equal(stdout, // JSSTYLED '[2012-02-08T12:56:52.856-10:00] INFO: myservice/123 on example.com: ' + 'My message\n'); t.end(); }); }); test('time: simple.log utc long', function (t) { exec(_('%s -o long --time utc %s/corpus/simple.log', BUNYAN, __dirname), {env: tzEnv}, function (err, stdout, stderr) { t.ifError(err) t.equal(stdout, // JSSTYLED '[2012-02-08T22:56:52.856Z] INFO: myservice/123 on example.com: ' + 'My message\n'); t.end(); }); }); test('time: simple.log local short', function (t) { exec(_('%s -o short -L %s/corpus/simple.log', BUNYAN, __dirname), {env: tzEnv}, function (err, stdout, stderr) { t.ifError(err) t.equal(stdout, '12:56:52.856 INFO myservice: ' + 'My message\n'); t.end(); }); }); test('time: simple.log utc short', function (t) { exec(_('%s -o short %s/corpus/simple.log', BUNYAN, __dirname), {env: tzEnv}, function (err, stdout, stderr) { t.ifError(err) t.equal(stdout, '22:56:52.856Z INFO myservice: ' + 'My message\n'); t.end(); }); }); suite.end(); }); test('simple.log with color', function (t) { exec(_('%s --color %s/corpus/simple.log', BUNYAN, __dirname), function (err, stdout, stderr) { t.ifError(err) t.equal(stdout, /* JSSTYLED */ '[2012-02-08T22:56:52.856Z] \u001b[36m INFO\u001b[39m: myservice/123 on example.com: \u001b[36mMy message\u001b[39m\n\u001b[0m'); t.end(); }); }); test('extrafield.log', function (t) { exec(_('%s %s/corpus/extrafield.log', BUNYAN, __dirname), function (err, stdout, stderr) { t.ifError(err) t.equal(stdout, '[2012-02-08T22:56:52.856Z] INFO: myservice/123 on example.com: ' + 'My message (extra=field)\n'); t.end(); }); }); test('extrafield.log with color', function (t) { exec(_('%s --color %s/corpus/extrafield.log', BUNYAN, __dirname), function (err, stdout, stderr) { t.ifError(err) t.equal(stdout, '[2012-02-08T22:56:52.856Z] \u001b[36m INFO\u001b[39m: ' + 'myservice/123 ' + 'on example.com: \u001b[36mMy message\u001b[39m' + ' (extra=field)\n\u001b[0m'); t.end(); }); }); test('bogus.log', function (t) { exec(_('%s %s/corpus/bogus.log', BUNYAN, __dirname), function (err, stdout, stderr) { t.ifError(err) t.equal(stdout, 'not a JSON line\n{"hi": "there"}\n'); t.end(); }); }); test('bogus.log -j', function (t) { exec(_('%s -j %s/corpus/bogus.log', BUNYAN, __dirname), function (err, stdout, stderr) { t.ifError(err) t.equal(stdout, 'not a JSON line\n{"hi": "there"}\n'); t.end(); }); }); test('all.log', function (t) { exec(_('%s %s/corpus/all.log', BUNYAN, __dirname), function (err, stdout, stderr) { // Just make sure don't blow up on this. t.ifError(err) t.end(); }); }); test('simple.log doesnotexist1.log doesnotexist2.log', function (t) { exec(_('%s %s/corpus/simple.log doesnotexist1.log doesnotexist2.log', BUNYAN, __dirname), function (err, stdout, stderr) { t.ok(err) t.equal(err.code, 2) t.equal(stdout, /* JSSTYLED */ '[2012-02-08T22:56:52.856Z] INFO: myservice/123 on example.com: My message\n'); // Note: node v0.6.10: // ENOENT, no such file or directory 'asdf.log' // but node v0.6.14: // ENOENT, open 'asdf.log' // io.js 2.2 (at least): // ENOENT: no such file or directory, open 'doesnotexist1.log' // in GitHub Actions windows-latest runner: // JSSTYLED // ENOENT: no such file or directory, open 'D:\\a\\node-bunyan\\node-bunyan\\doesnotexist1.log var matches = [ /^bunyan: ENOENT.*?, open '.*?doesnotexist1.log'/m, /^bunyan: ENOENT.*?, open '.*?doesnotexist2.log'/m, ]; matches.forEach(function (match) { t.ok(match.test(stderr), 'stderr matches ' + match.toString() + ', stderr=' + JSON .stringify(stderr)); }); t.end(); } ); }); test('multiple logs', function (t) { var cmd = _('%s %s/corpus/log1.log %s/corpus/log2.log', BUNYAN, __dirname, __dirname); exec(cmd, function (err, stdout, stderr) { t.ifError(err); t.equal(stdout, [ /* BEGIN JSSTYLED */ '[2012-05-08T16:57:55.586Z] INFO: agent1/73267 on headnode: message\n', '[2012-05-08T16:58:55.586Z] INFO: agent2/73267 on headnode: message\n', '[2012-05-08T17:01:49.339Z] INFO: agent2/73267 on headnode: message\n', '[2012-05-08T17:02:47.404Z] INFO: agent2/73267 on headnode: message\n', '[2012-05-08T17:02:49.339Z] INFO: agent1/73267 on headnode: message\n', '[2012-05-08T17:02:49.404Z] INFO: agent1/73267 on headnode: message\n', '[2012-05-08T17:02:49.404Z] INFO: agent1/73267 on headnode: message\n', '[2012-05-08T17:02:57.404Z] INFO: agent2/73267 on headnode: message\n', '[2012-05-08T17:08:01.105Z] INFO: agent2/76156 on headnode: message\n', /* END JSSTYLED */ ].join('')); t.end(); }); }); test('multiple logs, bunyan format', function (t) { var cmd = _('%s -o bunyan %s/corpus/log1.log %s/corpus/log2.log', BUNYAN, __dirname, __dirname); exec(cmd, function (err, stdout, stderr) { t.ifError(err); t.equal(stdout, [ /* BEGIN JSSTYLED */ '{"name":"agent1","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T16:57:55.586Z","v":0}', '{"name":"agent2","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T16:58:55.586Z","v":0}', '{"name":"agent2","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T17:01:49.339Z","v":0}', '{"name":"agent2","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T17:02:47.404Z","v":0}', '{"name":"agent1","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T17:02:49.339Z","v":0}', '{"name":"agent1","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T17:02:49.404Z","v":0}', '{"name":"agent1","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T17:02:49.404Z","v":0}', '{"name":"agent2","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T17:02:57.404Z","v":0}', '{"name":"agent2","pid":76156,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T17:08:01.105Z","v":0}', '' /* END JSSTYLED */ ].join('\n')); t.end(); }); }); test('log1.log.gz', function (t) { exec(_('%s %s/corpus/log1.log.gz', BUNYAN, __dirname), function (err, stdout, stderr) { t.ifError(err); t.equal(stdout, [ /* BEGIN JSSTYLED */ '[2012-05-08T16:57:55.586Z] INFO: agent1/73267 on headnode: message\n', '[2012-05-08T17:02:49.339Z] INFO: agent1/73267 on headnode: message\n', '[2012-05-08T17:02:49.404Z] INFO: agent1/73267 on headnode: message\n', '[2012-05-08T17:02:49.404Z] INFO: agent1/73267 on headnode: message\n', /* END JSSTYLED */ ].join('')); t.end(); }); }); test('mixed text and gzip logs', function (t) { var cmd = _('%s %s/corpus/log1.log.gz %s/corpus/log2.log', BUNYAN, __dirname, __dirname); exec(cmd, function (err, stdout, stderr) { t.ifError(err); t.equal(stdout, [ /* BEGIN JSSTYLED */ '[2012-05-08T16:57:55.586Z] INFO: agent1/73267 on headnode: message\n', '[2012-05-08T16:58:55.586Z] INFO: agent2/73267 on headnode: message\n', '[2012-05-08T17:01:49.339Z] INFO: agent2/73267 on headnode: message\n', '[2012-05-08T17:02:47.404Z] INFO: agent2/73267 on headnode: message\n', '[2012-05-08T17:02:49.339Z] INFO: agent1/73267 on headnode: message\n', '[2012-05-08T17:02:49.404Z] INFO: agent1/73267 on headnode: message\n', '[2012-05-08T17:02:49.404Z] INFO: agent1/73267 on headnode: message\n', '[2012-05-08T17:02:57.404Z] INFO: agent2/73267 on headnode: message\n', '[2012-05-08T17:08:01.105Z] INFO: agent2/76156 on headnode: message\n', /* END JSSTYLED */ ].join('')); t.end(); }); }); test('--level 40', function (t) { expect = [ /* BEGIN JSSTYLED */ '# levels\n', '[2012-02-08T22:56:53.856Z] WARN: myservice/123 on example.com: My message\n', '[2012-02-08T22:56:54.856Z] ERROR: myservice/123 on example.com: My message\n', '[2012-02-08T22:56:55.856Z] LVL55: myservice/123 on example.com: My message\n', '[2012-02-08T22:56:56.856Z] FATAL: myservice/123 on example.com: My message\n', '\n', '# extra fields\n', '\n', '# bogus\n', 'not a JSON line\n', '{"hi": "there"}\n' /* END JSSTYLED */ ].join(''); exec(_('%s -l 40 %s/corpus/all.log', BUNYAN, __dirname), function (err, stdout, stderr) { t.ifError(err); t.equal(stdout, expect); exec(_('%s --level 40 %s/corpus/all.log', BUNYAN, __dirname), function (err, stdout, stderr) { t.ifError(err); t.equal(stdout, expect); t.end(); }); }); }); test('--condition "this.level === 10 && this.pid === 123"', function (t) { var expect = [ '# levels\n', /* JSSTYLED */ '[2012-02-08T22:56:50.856Z] TRACE: myservice/123 on example.com: My message\n', '\n', '# extra fields\n', '\n', '# bogus\n', 'not a JSON line\n', '{"hi": "there"}\n' ].join(''); var cmd = _('%s -c "this.level === 10 && this.pid === 123"' + ' %s/corpus/all.log', BUNYAN, __dirname); exec(cmd, function (err, stdout, stderr) { t.ifError(err); t.equal(stdout, expect); var cmd = _( '%s --condition "this.level === 10 && this.pid === 123"' + ' %s/corpus/all.log', BUNYAN, __dirname); exec(cmd, function (err, stdout, stderr) { t.ifError(err); t.equal(stdout, expect); t.end(); }); }); }); test('--condition "this.level === TRACE', function (t) { var expect = [ '# levels\n', /* JSSTYLED */ '[2012-02-08T22:56:50.856Z] TRACE: myservice/123 on example.com: My message\n', '\n', '# extra fields\n', '\n', '# bogus\n', 'not a JSON line\n', '{"hi": "there"}\n' ].join(''); var cmd = _('%s -c "this.level === TRACE" %s/corpus/all.log', BUNYAN, __dirname); exec(cmd, function (err, stdout, stderr) { t.ifError(err); t.equal(stdout, expect); t.end(); }); }); // multiple test('multiple --conditions', function (t) { var expect = [ '# levels\n', /* JSSTYLED */ '[2012-02-08T22:56:53.856Z] WARN: myservice/123 on example.com: My message\n', '\n', '# extra fields\n', '\n', '# bogus\n', 'not a JSON line\n', '{"hi": "there"}\n' ].join(''); exec(_('%s %s/corpus/all.log -c "this.level === 40" -c "this.pid === 123"', BUNYAN, __dirname), function (err, stdout, stderr) { t.ifError(err); t.equal(stdout, expect); t.end(); }); }); // https://github.com/trentm/node-bunyan/issues/30 // // One of the records in corpus/withreq.log has a 'req' // field with no 'headers'. Ditto for the 'res' field. test('robust req handling', function (t) { var expect = [ /* BEGIN JSSTYLED */ '[2012-08-08T10:25:47.636Z] DEBUG: amon-master/12859 on 9724a190-27b6-4fd8-830b-a574f839c67d: headAgentProbes respond (req_id=cce79d15-ffc2-487c-a4e4-e940bdaac31e, route=HeadAgentProbes, contentMD5=11FxOYiYfpMxmANj4kGJzg==)', '[2012-08-08T10:25:47.637Z] INFO: amon-master/12859 on 9724a190-27b6-4fd8-830b-a574f839c67d: HeadAgentProbes handled: 200 (req_id=cce79d15-ffc2-487c-a4e4-e940bdaac31e, audit=true, remoteAddress=10.2.207.2, remotePort=50394, latency=3, secure=false, _audit=true, req.version=*)', ' HEAD /agentprobes?agent=ccf92af9-0b24-46b6-ab60-65095fdd3037 HTTP/1.1', ' accept: application/json', ' content-type: application/json', ' host: 10.2.207.16', ' connection: keep-alive', ' --', ' HTTP/1.1 200 OK', ' content-md5: 11FxOYiYfpMxmANj4kGJzg==', ' access-control-allow-origin: *', ' access-control-allow-headers: Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version', ' access-control-allow-methods: HEAD', ' access-control-expose-headers: X-Api-Version, X-Request-Id, X-Response-Time', ' connection: Keep-Alive', ' date: Wed, 08 Aug 2012 10:25:47 GMT', ' server: Amon Master/1.0.0', ' x-request-id: cce79d15-ffc2-487c-a4e4-e940bdaac31e', ' x-response-time: 3', ' --', ' route: {', ' "name": "HeadAgentProbes",', ' "version": false', ' }', '[2012-08-08T10:25:47.637Z] INFO: amon-master/12859 on 9724a190-27b6-4fd8-830b-a574f839c67d: HeadAgentProbes handled: 200 (req_id=cce79d15-ffc2-487c-a4e4-e940bdaac31e, audit=true, remoteAddress=10.2.207.2, remotePort=50394, latency=3, secure=false, _audit=true, req.version=*)', ' HEAD /agentprobes?agent=ccf92af9-0b24-46b6-ab60-65095fdd3037 HTTP/1.1', ' --', ' HTTP/1.1 200 OK', ' --', ' route: {', ' "name": "HeadAgentProbes",', ' "version": false', ' }' /* END JSSTYLED */ ].join('\n') + '\n'; exec(_('%s %s/corpus/withreq.log', BUNYAN, __dirname), function (err, stdout, stderr) { t.ifError(err); t.equal(stdout, expect); t.end(); }); }); // Some past crashes from issues. test('should not crash on corpus/old-crashers/*.log', function (t) { var oldCrashers = fs.readdirSync( path.resolve(__dirname, 'corpus/old-crashers')) .filter(function (f) { return f.slice(-4) === '.log'; }); vasync.forEachPipeline({ inputs: oldCrashers, func: function (logName, next) { exec(_('%s %s/corpus/old-crashers/%s', BUNYAN, __dirname, logName), function (err, stdout, stderr) { next(err); }); } }, function (err, results) { t.ifError(err); t.end(); }); }); test('should only show nonempty response bodies', function (t) { var expect = [ /* BEGIN JSSTYLED */ '[2016-02-10T07:28:41.419Z] INFO: myservice/123 on example.com: UnauthorizedError', ' HTTP/1.1 401 Unauthorized', ' content-type: text/plain', ' date: Sat, 07 Mar 2015 06:58:43 GMT', '[2016-02-10T07:28:41.419Z] INFO: myservice/123 on example.com: hello', ' HTTP/1.1 200 OK', ' content-type: text/plain', ' content-length: 0', ' date: Sat, 07 Mar 2015 06:58:43 GMT', ' ', ' hello', '[2016-02-10T07:28:41.419Z] INFO: myservice/123 on example.com: UnauthorizedError', ' HTTP/1.1 401 Unauthorized', ' content-type: text/plain', ' date: Sat, 07 Mar 2015 06:58:43 GMT' /* END JSSTYLED */ ].join('\n') + '\n'; exec(_('%s %s/corpus/content-length-0-res.log', BUNYAN, __dirname), function (err, stdout, stderr) { t.ifError(err); t.equal(stdout, expect); t.end(); }); }); node-bunyan-2.0.5/test/corpus/000077500000000000000000000000001377616564400162525ustar00rootroot00000000000000node-bunyan-2.0.5/test/corpus/all.log000066400000000000000000000035421377616564400175310ustar00rootroot00000000000000# levels {"name":"myservice","pid":123,"hostname":"example.com","level":10,"msg":"My message","time":"2012-02-08T22:56:50.856Z","v":0} {"name":"myservice","pid":123,"hostname":"example.com","level":20,"msg":"My message","time":"2012-02-08T22:56:51.856Z","v":0} {"name":"myservice","pid":123,"hostname":"example.com","level":30,"msg":"My message","time":"2012-02-08T22:56:52.856Z","v":0} {"name":"myservice","pid":123,"hostname":"example.com","level":40,"msg":"My message","time":"2012-02-08T22:56:53.856Z","v":0} {"name":"myservice","pid":123,"hostname":"example.com","level":50,"msg":"My message","time":"2012-02-08T22:56:54.856Z","v":0} {"name":"myservice","pid":123,"hostname":"example.com","level":55,"msg":"My message","time":"2012-02-08T22:56:55.856Z","v":0} {"name":"myservice","pid":123,"hostname":"example.com","level":60,"msg":"My message","time":"2012-02-08T22:56:56.856Z","v":0} # extra fields {"name":"myservice","pid":123,"hostname":"example.com","level":30,"one":"short","msg":"My message","time":"2012-02-08T22:56:52.856Z","v":0} {"name":"myservice","pid":123,"hostname":"example.com","level":30,"two":"short with space","msg":"My message","time":"2012-02-08T22:56:52.856Z","v":0} {"name":"myservice","pid":123,"hostname":"example.com","level":30,"three":"multi\nline","msg":"My message","time":"2012-02-08T22:56:52.856Z","v":0} {"name":"myservice","pid":123,"hostname":"example.com","level":30,"four":"over 50 chars long long long long long long long long long","msg":"My message","time":"2012-02-08T22:56:52.856Z","v":0} {"name":"myservice","pid":123,"hostname":"example.com","level":30,"five":{"a": "json object"},"msg":"My message","time":"2012-02-08T22:56:52.856Z","v":0} {"name":"myservice","pid":123,"hostname":"example.com","level":30,"six":["a", "json", "array"],"msg":"My message","time":"2012-02-08T22:56:52.856Z","v":0} # bogus not a JSON line {"hi": "there"} node-bunyan-2.0.5/test/corpus/bogus.log000066400000000000000000000000401377616564400200660ustar00rootroot00000000000000not a JSON line {"hi": "there"} node-bunyan-2.0.5/test/corpus/client-req-with-address.log000066400000000000000000000004671377616564400234230ustar00rootroot00000000000000{"name":"minfo","hostname":"sharptooth.local","pid":66266,"level":10,"client_req":{"method":"HEAD","url":"/dap/stor","address":"127.0.0.1","headers":{"accept":"application/json, */*","host":"foo.example.com","date":"Fri, 12 May 2017 23:59:15 GMT"}},"msg":"request sent","time":"2017-05-12T23:59:15.877Z","v":0} node-bunyan-2.0.5/test/corpus/clientreqres.log000066400000000000000000000006771377616564400214670ustar00rootroot00000000000000{"name":"aclientreq","hostname":"danger0.local","pid":23280,"level":10,"client_req":{"method":"GET","url":"/--ping"},"msg":"request sent","time":"2016-02-10T07:28:40.510Z","v":0} {"name":"aclientreq","hostname":"danger0.local","pid":23280,"level":10,"client_res":{"statusCode":200,"headers":{"request-id":"e8a5a700-cfc7-11e5-a3dc-3b85d20f26ef","content-type":"application/json"}},"msg":"Response received","time":"2016-02-10T07:28:41.419Z","v":0} node-bunyan-2.0.5/test/corpus/content-length-0-res.log000066400000000000000000000013741377616564400226370ustar00rootroot00000000000000{"name":"myservice","hostname":"example.com","pid":123,"level":30,"client_res":{"statusCode":401,"headers":{"content-type":"text/plain","date":"Sat, 07 Mar 2015 06:58:43 GMT"},"body":""},"msg":"UnauthorizedError","time":"2016-02-10T07:28:41.419Z","v":0} {"name":"myservice","hostname":"example.com","pid":123,"level":30,"client_res":{"statusCode":200,"headers":{"content-type":"text/plain","content-length":0,"date":"Sat, 07 Mar 2015 06:58:43 GMT"},"body":"hello"},"msg":"hello","time":"2016-02-10T07:28:41.419Z","v":0} {"name":"myservice","hostname":"example.com","pid":123,"level":30,"client_res":{"statusCode":401,"headers":{"content-type":"text/plain","date":"Sat, 07 Mar 2015 06:58:43 GMT"}},"msg":"UnauthorizedError","time":"2016-02-10T07:28:41.419Z","v":0} node-bunyan-2.0.5/test/corpus/extrafield.log000066400000000000000000000002161377616564400211030ustar00rootroot00000000000000{"name":"myservice","pid":123,"hostname":"example.com","level":30,"extra":"field","msg":"My message","time":"2012-02-08T22:56:52.856Z","v":0} node-bunyan-2.0.5/test/corpus/log1.log000066400000000000000000000007341377616564400176230ustar00rootroot00000000000000{"name":"agent1","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T16:57:55.586Z","v":0} {"name":"agent1","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T17:02:49.339Z","v":0} {"name":"agent1","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T17:02:49.404Z","v":0} {"name":"agent1","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T17:02:49.404Z","v":0} node-bunyan-2.0.5/test/corpus/log1.log.gz000066400000000000000000000002241377616564400202340ustar00rootroot00000000000000Olog1.logō; 0=ƫc+@*`#}aN!1s F _8ļGno@277T[V(U-YOvpS3 ^߷P{'/x>node-bunyan-2.0.5/test/corpus/log2.log000066400000000000000000000011231377616564400176150ustar00rootroot00000000000000{"name":"agent2","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T16:58:55.586Z","v":0} {"name":"agent2","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T17:01:49.339Z","v":0} {"name":"agent2","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T17:02:47.404Z","v":0} {"name":"agent2","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T17:02:57.404Z","v":0} {"name":"agent2","pid":76156,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T17:08:01.105Z","v":0} node-bunyan-2.0.5/test/corpus/non-object-res.log000066400000000000000000000004541377616564400216050ustar00rootroot00000000000000{"name":"cnapi.get_existing_nics","job_uuid":"3499b13e-dbca-4331-b13a-f164c0da320a","hostname":"710c784f-6aa5-428c-9074-e046c3af884e","pid":24440,"level":30,"nic":"","res":"error: Unknown nic \"020820d753e0\"","msg":"got existing: 02:08:20:d7:53:e0","time":"2012-10-10T16:14:07.610Z","v":0} node-bunyan-2.0.5/test/corpus/old-crashers/000077500000000000000000000000001377616564400206405ustar00rootroot00000000000000node-bunyan-2.0.5/test/corpus/old-crashers/139.log000066400000000000000000000011041377616564400216530ustar00rootroot00000000000000{"name":"wgaf","hostname":"vmac.local","pid":38947,"level":30,"res":{"statusCode":201,"header":{"connection":"close","access-control-allow-origin":"*","access-control-allow-headers":"Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, Api-Version, Response-Time","access-control-allow-methods":"POST","access-control-expose-headers":"Api-Version, Request-Id, Response-Time","date":"Wed, 23 Apr 2014 13:11:53 GMT","server":"wgaf","request-id":"d6cfe8a0-cae8-11e3-9f88-ff29f9d02103","response-time":7}},"msg":"finished","time":"2014-04-23T13:11:53.644Z","v":0} node-bunyan-2.0.5/test/corpus/old-crashers/144.log000066400000000000000000000031141377616564400216520ustar00rootroot00000000000000{"name":"blah_api","hostname":"myhost.home","pid":69312,"level":30,"remote-address":"127.0.0.1","ip":"127.0.0.1","method":"GET","url":"/v0/opps?q=software","referer":"-","user-agent":{"family":"Chrome","major":"34","minor":"0","patch":"1847","device":{"family":"Other"},"os":{"family":"Mac OS X","major":"10","minor":"9","patch":"2"}},"body":{},"short-body":"{}","http-version":"1.1","response-time":232,"status-code":304,"req-headers":{"host":"localhost:3000","connection":"keep-alive","cache-control":"max-age=0","accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.137 Safari/537.36","accept-encoding":"gzip,deflate,sdch","accept-language":"en-US,en;q=0.8","cookie":"__atuvc=2%7C47; csrftoken=XXX; _ga=GA1.1.2104887628.1398883175","if-none-match":"\"-929537647\""},"res-headers":{"x-powered-by":"Express","access-control-allow-origin":"*","access-control-allow-headers":"X-Requested-With","etag":"\"-929537647\""},"req":{"method":"GET","url":"/v0/opps?q=software","headers":"[Circular]","remoteAddress":"127.0.0.1","remotePort":58387},"res":{"statusCode":304,"header":"HTTP/1.1 304 Not Modified\r\nX-Powered-By: Express\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Headers: X-Requested-With\r\nETag: \"-929537647\"\r\nDate: Fri, 16 May 2014 03:04:13 GMT\r\nConnection: keep-alive\r\n\r\n"},"incoming":"<--","msg":"127.0.0.1 <-- GET /v0/opps?q=software HTTP/1.1 304 - - Chrome 34.0 Mac OS X 10.9.2 232 ms","time":"2014-05-16T03:04:13.961Z","v":0} node-bunyan-2.0.5/test/corpus/old-crashers/233.log000066400000000000000000000006071377616564400216550ustar00rootroot00000000000000{"name":"server","hostname":"iZ25apt4ethZ","pid":958,"level":30,"req":{"method":"GET","url":"/ground/fetch?id=null","headers":{"host":"123.123.123.123","connection":"Keep-Alive","accept-encoding":"gzip"},"remoteAddress":"::ffff:123.123.123.123"},"res":{"headers":true,"content":{"status":0,"message":"success","messages":[]}},"msg":"Time used: 30ms","time":"2015-03-07T07:28:32.431Z","v":0} node-bunyan-2.0.5/test/corpus/old-crashers/242.log000066400000000000000000000005361377616564400216560ustar00rootroot00000000000000{"name":"AKP48","module":"Server","hostname":"AKP48.akpwebdesign.com","pid":32421,"level":60,"err":{"message":"Function.prototype.apply: Arguments list has wrong type","name":"TypeError","stack":[{},{},{},{},{},{}]},"msg":"Exception caught: TypeError: Function.prototype.apply: Arguments list has wrong type","time":"2015-04-13T04:03:46.206Z","v":0} node-bunyan-2.0.5/test/corpus/old-crashers/244.log000066400000000000000000000004131377616564400216520ustar00rootroot00000000000000{"name":"multichannel","hostname":"macbook-dev","pid":44973,"level":30,"res":{"statusCode":401,"header":null},"response":"{\"error\":\"InvalidCredentials\",\"description\":\"The access token provided has expired.\"}","msg":"","time":"2015-04-15T10:37:39.557Z","v":0} node-bunyan-2.0.5/test/corpus/old-crashers/README.md000066400000000000000000000001331377616564400221140ustar00rootroot00000000000000Log lines that used to crash `bunyan`. Typically the file name is the bunyan issue number. node-bunyan-2.0.5/test/corpus/res-header.log000066400000000000000000000004451377616564400207770ustar00rootroot00000000000000{"name":"res-header","hostname":"danger0.local","pid":76488,"level":30,"res":{"statusCode":200,"header":"HTTP/1.1 200 OK\r\nFoo: bar\r\nDate: Wed, 02 Aug 2017 22:37:34 GMT\r\nConnection: keep-alive\r\nContent-Length: 21\r\n\r\n"},"msg":"response sent","time":"2017-08-02T22:37:34.798Z","v":0} node-bunyan-2.0.5/test/corpus/res-without-header.log000066400000000000000000000002371377616564400224770ustar00rootroot00000000000000{"name":"res-header","hostname":"danger0.local","pid":76488,"level":30,"res":{"statusCode":200},"msg":"response sent","time":"2017-08-02T22:37:34.798Z","v":0} node-bunyan-2.0.5/test/corpus/simple.log000066400000000000000000000001761377616564400202520ustar00rootroot00000000000000{"name":"myservice","pid":123,"hostname":"example.com","level":30,"msg":"My message","time":"2012-02-08T22:56:52.856Z","v":0} node-bunyan-2.0.5/test/corpus/withreq.log000066400000000000000000000037151377616564400204460ustar00rootroot00000000000000{"name":"amon-master","hostname":"9724a190-27b6-4fd8-830b-a574f839c67d","pid":12859,"route":"HeadAgentProbes","req_id":"cce79d15-ffc2-487c-a4e4-e940bdaac31e","level":20,"contentMD5":"11FxOYiYfpMxmANj4kGJzg==","msg":"headAgentProbes respond","time":"2012-08-08T10:25:47.636Z","v":0} {"name":"amon-master","hostname":"9724a190-27b6-4fd8-830b-a574f839c67d","pid":12859,"audit":true,"level":30,"remoteAddress":"10.2.207.2","remotePort":50394,"req_id":"cce79d15-ffc2-487c-a4e4-e940bdaac31e","req":{"method":"HEAD","url":"/agentprobes?agent=ccf92af9-0b24-46b6-ab60-65095fdd3037","headers":{"accept":"application/json","content-type":"application/json","host":"10.2.207.16","connection":"keep-alive"},"httpVersion":"1.1","trailers":{},"version":"*"},"res":{"statusCode":200,"headers":{"content-md5":"11FxOYiYfpMxmANj4kGJzg==","access-control-allow-origin":"*","access-control-allow-headers":"Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version","access-control-allow-methods":"HEAD","access-control-expose-headers":"X-Api-Version, X-Request-Id, X-Response-Time","connection":"Keep-Alive","date":"Wed, 08 Aug 2012 10:25:47 GMT","server":"Amon Master/1.0.0","x-request-id":"cce79d15-ffc2-487c-a4e4-e940bdaac31e","x-response-time":3},"trailer":false},"route":{"name":"HeadAgentProbes","version":false},"latency":3,"secure":false,"_audit":true,"msg":"HeadAgentProbes handled: 200","time":"2012-08-08T10:25:47.637Z","v":0} {"name":"amon-master","hostname":"9724a190-27b6-4fd8-830b-a574f839c67d","pid":12859,"audit":true,"level":30,"remoteAddress":"10.2.207.2","remotePort":50394,"req_id":"cce79d15-ffc2-487c-a4e4-e940bdaac31e","req":{"method":"HEAD","url":"/agentprobes?agent=ccf92af9-0b24-46b6-ab60-65095fdd3037","httpVersion":"1.1","trailers":{},"version":"*"},"res":{"statusCode":200,"trailer":false},"route":{"name":"HeadAgentProbes","version":false},"latency":3,"secure":false,"_audit":true,"msg":"HeadAgentProbes handled: 200","time":"2012-08-08T10:25:47.637Z","v":0} node-bunyan-2.0.5/test/ctor.test.js000066400000000000000000000114071377616564400172250ustar00rootroot00000000000000/* * Copyright 2020 Trent Mick * * Test type checking on creation of the Logger. */ var test = require('tap').test; var bunyan = require('../lib/bunyan') var Logger = bunyan; test('ensure Logger creation options', function (t) { t.throws(function () { new Logger(); }, /options \(object\) is required/, 'no options should throw'); t.throws(function () { new Logger({}); }, /options\.name \(string\) is required/, 'no options.name should throw'); t.doesNotThrow(function () { new Logger({name: 'foo'}); }, 'just options.name should be sufficient'); var options = {name: 'foo', stream: process.stdout, streams: []}; t.throws(function () { new Logger(options); }, /* JSSTYLED */ /cannot mix "streams" and "stream" options/, 'cannot use "stream" and "streams"'); // https://github.com/trentm/node-bunyan/issues/3 options = {name: 'foo', streams: {}}; t.throws(function () { new Logger(options); }, /invalid options.streams: must be an array/, '"streams" must be an array'); options = {name: 'foo', serializers: 'a string'}; t.throws(function () { new Logger(options); }, /invalid options.serializers: must be an object/, '"serializers" cannot be a string'); options = {name: 'foo', serializers: [1, 2, 3]}; t.throws(function () { new Logger(options); }, /invalid options.serializers: must be an object/, '"serializers" cannot be an array'); t.end(); }); test('ensure Logger constructor is safe without new', function (t) { t.doesNotThrow(function () { Logger({name: 'foo'}); }, 'constructor should call self with new if necessary'); t.end(); }); test('ensure Logger creation options (createLogger)', function (t) { t.throws(function () { bunyan.createLogger(); }, /options \(object\) is required/, 'no options should throw'); t.throws(function () { bunyan.createLogger({}); }, /options\.name \(string\) is required/, 'no options.name should throw'); t.doesNotThrow(function () { bunyan.createLogger({name: 'foo'}); }, 'just options.name should be sufficient'); var options = {name: 'foo', stream: process.stdout, streams: []}; t.throws(function () { bunyan.createLogger(options); }, /* JSSTYLED */ /cannot mix "streams" and "stream" options/, 'cannot use "stream" and "streams"'); // https://github.com/trentm/node-bunyan/issues/3 options = {name: 'foo', streams: {}}; t.throws(function () { bunyan.createLogger(options); }, /invalid options.streams: must be an array/, '"streams" must be an array'); options = {name: 'foo', serializers: 'a string'}; t.throws(function () { bunyan.createLogger(options); }, /invalid options.serializers: must be an object/, '"serializers" cannot be a string'); options = {name: 'foo', serializers: [1, 2, 3]}; t.throws(function () { bunyan.createLogger(options); }, /invalid options.serializers: must be an object/, '"serializers" cannot be an array'); t.end(); }); test('ensure Logger child() options', function (t) { var log = new Logger({name: 'foo'}); t.doesNotThrow(function () { log.child(); }, 'no options should be fine'); t.doesNotThrow(function () { log.child({}); }, 'empty options should be fine too'); t.throws(function () { log.child({name: 'foo'}); }, /invalid options.name: child cannot set logger name/, 'child cannot change name'); var options = {stream: process.stdout, streams: []}; t.throws(function () { log.child(options); }, /* JSSTYLED */ /cannot mix "streams" and "stream" options/, 'cannot use "stream" and "streams"'); // https://github.com/trentm/node-bunyan/issues/3 options = {streams: {}}; t.throws(function () { log.child(options); }, /invalid options.streams: must be an array/, '"streams" must be an array'); options = {serializers: 'a string'}; t.throws(function () { log.child(options); }, /invalid options.serializers: must be an object/, '"serializers" cannot be a string'); options = {serializers: [1, 2, 3]}; t.throws(function () { log.child(options); }, /invalid options.serializers: must be an object/, '"serializers" cannot be an array'); t.end(); }); test('ensure Logger() rejects non-Logger parents', function (t) { var dad = new Logger({name: 'dad', streams: []}); t.throws(function () { new Logger({}, {}); }, /invalid Logger creation: do not pass a second arg/, 'Logger arguments must be valid'); t.doesNotThrow(function () { new Logger(dad, {}); }, 'Logger allows Logger instance as parent'); t.end(); }); node-bunyan-2.0.5/test/cycles.test.js000066400000000000000000000046111377616564400175370ustar00rootroot00000000000000/* * Copyright 2020 Trent Mick. * * Make sure cycles are safe. */ var Logger = require('../lib/bunyan.js'); var test = require('tap').test; var Stream = require('stream').Stream; var outstr = new Stream; outstr.writable = true; var output = []; outstr.write = function (c) { output.push(JSON.parse(c + '')); }; outstr.end = function (c) { if (c) this.write(c); this.emit('end'); }; var expect = [ { 'name': 'blammo', 'level': 30, 'msg': 'bango { bang: \'boom\', KABOOM: [Circular] }', 'v': 0 }, { 'name': 'blammo', 'level': 30, 'msg': 'kaboom { bang: \'boom\', KABOOM: [Circular] }', 'v': 0 }, { 'name': 'blammo', 'level': 30, 'bang': 'boom', 'KABOOM': { 'bang': 'boom', 'KABOOM': '[Circular]' }, 'msg': '', 'v': 0 } ]; var log = new Logger({ name: 'blammo', streams: [ { type: 'stream', level: 'info', stream: outstr } ] }); test('cycles', function (t) { var rec; outstr.on('end', function () { output.forEach(function (o, i) { // Drop variable parts for comparison. delete o.hostname; delete o.pid; delete o.time; // In change https://github.com/nodejs/node/pull/27685 (part of // node v14), how objects with circular references are stringified // with `util.inspect` changed. if (Number(process.versions.node.split('.')[0]) >= 14) { expect[i].msg = expect[i].msg.replace( // JSSTYLED /{ bang: 'boom', KABOOM: \[Circular\] }/, ' { bang: \'boom\', KABOOM: [Circular *1] }' ); } t.equal(JSON.stringify(o), JSON.stringify(expect[i]), 'log record ' + i + ' matches'); }); t.end(); }); var obj = { bang: 'boom' }; obj.KABOOM = obj; // This creates a circular reference. log.info('bango', obj); log.info('kaboom', obj.KABOOM); log.info(obj); t.ok('did not throw'); outstr.end(); }); node-bunyan-2.0.5/test/dtrace/000077500000000000000000000000001377616564400162015ustar00rootroot00000000000000node-bunyan-2.0.5/test/dtrace/dtrace.test.js000066400000000000000000000072351377616564400207660ustar00rootroot00000000000000/* * Copyright 2020 Trent Mick * * If available, test dtrace support. */ var spawn = require('child_process').spawn; var format = require('util').format; var test = require('tap').test; var bunyan = require('../../lib/bunyan'); // Determine if we can run the dtrace tests. var dtracePlats = ['sunos', 'darwin', 'freebsd']; var runDtraceTests = true; try { require('dtrace-provider'); } catch (e) { console.log('# skip dtrace tests: no dtrace-provider module'); runDtraceTests = false; } if (!runDtraceTests) { /* pass through */ } else if (dtracePlats.indexOf(process.platform) === -1) { console.log('# skip dtrace tests: not on a platform with dtrace'); runDtraceTests = false; } else if (process.env.SKIP_DTRACE) { console.log('# skip dtrace tests: SKIP_DTRACE envvar set'); runDtraceTests = false; } else if (process.getgid() !== 0) { console.log('# skip dtrace tests: gid is not 0, run with `sudo`'); runDtraceTests = false; } if (runDtraceTests) { test('basic dtrace', function (t) { var argv = ['dtrace', '-Z', '-x', 'strsize=4k', '-qn', 'bunyan$target:::log-*{printf("%s", copyinstr(arg0))}', '-c', format('node %s/log-some.js', __dirname)]; var dtrace = spawn(argv[0], argv.slice(1)); //console.error('ARGV: %j', argv); //console.error('CMD: %s', argv.join(' ')); var traces = []; dtrace.stdout.on('data', function (data) { //console.error('DTRACE STDOUT:', data.toString()); traces.push(data.toString()); }); dtrace.stderr.on('data', function (data) { console.error('DTRACE STDERR:', data.toString()); }); dtrace.on('exit', function (code) { t.notOk(code, 'dtrace exited cleanly'); traces = traces.join('').split('\n') .filter(function (t) { return t.trim().length }) .map(function (t) { return JSON.parse(t) }); t.equal(traces.length, 2, 'got 2 log records'); if (traces.length) { t.equal(traces[0].level, bunyan.DEBUG); t.equal(traces[0].foo, 'bar'); t.equal(traces[1].level, bunyan.TRACE); t.equal(traces[1].msg, 'hi at trace'); } t.end(); }); }); /* * Run a logger that logs a couple records every second. * Then run `bunyan -p PID` to capture. * Let those run for a few seconds to ensure dtrace has time to attach and * capture something. */ test('bunyan -p', function (t) { var p = spawn('node', [__dirname + '/log-some-loop.js']); var bunyanP = spawn('node', [__dirname + '/../bin/bunyan', '-p', String(p.pid), '-0']); var traces = []; bunyanP.stdout.on('data', function (data) { //console.error('BUNYAN -P STDOUT:', data.toString()); traces.push(data.toString()); }); bunyanP.stderr.on('data', function (data) { console.error('BUNYAN -P STDERR:', data.toString()); }); bunyanP.on('exit', function (code) { traces = traces.join('').split('\n') .filter(function (t) { return t.trim().length }) .map(function (t) { return JSON.parse(t) }); t.ok(traces.length >= 3, 'got >=3 log records: ' + traces.length); if (traces.length >= 3) { if (traces[0].level !== bunyan.DEBUG) { traces.shift(); } t.equal(traces[0].level, bunyan.DEBUG); t.equal(traces[0].foo, 'bar'); t.equal(traces[1].level, bunyan.TRACE); t.equal(traces[1].msg, 'hi at trace'); } t.end(); }); // Give it a few seconds to ensure we get some traces. setTimeout(function () { p.kill(); bunyanP.kill(); }, 5000); }); } /* end of `if (runDtraceTests)` */ node-bunyan-2.0.5/test/error-event.test.js000066400000000000000000000102051377616564400205210ustar00rootroot00000000000000/* * Copyright 2020 Trent Mick * * Test emission and handling of 'error' event in a logger with a 'path' * stream. */ var EventEmitter = require('events').EventEmitter; var test = require('tap').test; var util = require('util'); var bunyan = require('../lib/bunyan'); var BOGUS_PATH = '/this/path/is/bogus.log'; test('error event on file stream (reemitErrorEvents=undefined)', function (t) { var log = bunyan.createLogger( {name: 'error-event-1', streams: [ {path: BOGUS_PATH} ]}); log.on('error', function (err, stream) { t.ok(err, 'got err in error event: ' + err); t.equal(err.code, 'ENOENT', 'error code is ENOENT'); t.ok(stream, 'got a stream argument'); t.equal(stream.path, BOGUS_PATH); t.equal(stream.type, 'file'); t.end(); }); log.info('info log message'); }); test('error event on file stream (reemitErrorEvents=true)', function (t) { var log = bunyan.createLogger({ name: 'error-event-2', streams: [ { path: BOGUS_PATH, reemitErrorEvents: true } ] }); log.on('error', function (err, stream) { t.ok(err, 'got err in error event: ' + err); t.equal(err.code, 'ENOENT', 'error code is ENOENT'); t.ok(stream, 'got a stream argument'); t.equal(stream.path, BOGUS_PATH); t.equal(stream.type, 'file'); t.end(); }); log.info('info log message'); }); test('error event on file stream (reemitErrorEvents=false)', function (t) { var log = bunyan.createLogger({ name: 'error-event-3', streams: [ { path: BOGUS_PATH, reemitErrorEvents: false } ] }); // Hack into the underlying created file stream to catch the error event. log.streams[0].stream.on('error', function (err) { t.ok(err, 'got error event on the file stream'); t.end(); }); log.on('error', function (err, stream) { t.fail('should not have gotten error event on logger'); t.end(); }); log.info('info log message'); }); function MyErroringStream() {} util.inherits(MyErroringStream, EventEmitter); MyErroringStream.prototype.write = function (rec) { this.emit('error', new Error('boom')); } test('error event on raw stream (reemitErrorEvents=undefined)', function (t) { var estream = new MyErroringStream(); var log = bunyan.createLogger({ name: 'error-event-raw', streams: [ { stream: estream, type: 'raw' } ] }); estream.on('error', function (err) { t.ok(err, 'got error event on the raw stream'); t.end(); }); log.on('error', function (err, stream) { t.fail('should not have gotten error event on logger'); t.end(); }); log.info('info log message'); }); test('error event on raw stream (reemitErrorEvents=false)', function (t) { var estream = new MyErroringStream(); var log = bunyan.createLogger({ name: 'error-event-raw', streams: [ { stream: estream, type: 'raw', reemitErrorEvents: false } ] }); estream.on('error', function (err) { t.ok(err, 'got error event on the raw stream'); t.end(); }); log.on('error', function (err, stream) { t.fail('should not have gotten error event on logger'); t.end(); }); log.info('info log message'); }); test('error event on raw stream (reemitErrorEvents=true)', function (t) { var estream = new MyErroringStream(); var log = bunyan.createLogger({ name: 'error-event-raw', streams: [ { stream: estream, type: 'raw', reemitErrorEvents: true } ] }); log.on('error', function (err, stream) { t.ok(err, 'got err in error event: ' + err); t.equal(err.message, 'boom'); t.ok(stream, 'got a stream argument'); t.ok(stream.stream instanceof MyErroringStream); t.equal(stream.type, 'raw'); t.end(); }); log.info('info log message'); }); node-bunyan-2.0.5/test/level.test.js000066400000000000000000000046231377616564400173670ustar00rootroot00000000000000/* * Copyright 2020 Trent Mick * * Test the `log.level(...)`. */ var test = require('tap').test; var util = require('util'), format = util.format, inspect = util.inspect; var p = console.log; var bunyan = require('../lib/bunyan'); // ---- test boolean `log.()` calls var log1 = bunyan.createLogger({ name: 'log1', streams: [ { path: __dirname + '/level.test.log1.log', level: 'info' } ] }); test('log.level() -> level num', function (t) { t.equal(log1.level(), bunyan.INFO); t.end(); }); test('log.level()', function (t) { log1.level(bunyan.DEBUG); t.equal(log1.level(), bunyan.DEBUG); t.end(); }); test('log.level()', function (t) { log1.level(10); t.equal(log1.level(), bunyan.TRACE); t.end(); }); test('log.level()', function (t) { log1.level('error'); t.equal(log1.level(), bunyan.ERROR); t.end(); }); // A trick to turn logging off. // See . test('log.level(FATAL + 1)', function (t) { log1.level(bunyan.FATAL + 1); t.equal(log1.level(), bunyan.FATAL + 1); t.end(); }); test('log.level()', function (t) { log1.level(0); t.equal(log1.level(), 0); log1.level(Number.MAX_VALUE); t.equal(log1.level(), Number.MAX_VALUE); log1.level(Infinity); t.equal(log1.level(), Infinity); t.end(); }); test('log.level()', function (t) { t.throws(function () { var log = bunyan.createLogger({name: 'invalid', level: 'booga'}); // JSSTYLED }, /unknown level name: "booga"/); t.throws(function () { var log = bunyan.createLogger({name: 'invalid', level: []}); }, /cannot resolve level: invalid arg \(object\): \[\]/); t.throws(function () { var log = bunyan.createLogger({name: 'invalid', level: true}); }, /cannot resolve level: invalid arg \(boolean\): true/); t.throws(function () { var log = bunyan.createLogger({name: 'invalid', level: -1}); }, /level is not a positive integer: -1/); t.throws(function () { var log = bunyan.createLogger({name: 'invalid', level: 3.14}); }, /level is not a positive integer: 3.14/); t.throws(function () { var log = bunyan.createLogger({name: 'invalid', level: -Infinity}); }, /level is not a positive integer: -Infinity/); t.end(); }); node-bunyan-2.0.5/test/log-some-loop.js000066400000000000000000000006521377616564400177710ustar00rootroot00000000000000 // A helper script to log a few times, pause, repeat. We attempt to NOT emit // to stdout or stderr because this is used for dtrace testing // and we don't want to mix output. var bunyan = require('../lib/bunyan'); var log = bunyan.createLogger({ name: 'play', serializers: bunyan.stdSerializers }); setInterval(function logSome() { log.debug({foo: 'bar'}, 'hi at debug') log.trace('hi at trace') }, 1000); node-bunyan-2.0.5/test/log-some.js000066400000000000000000000005571377616564400170260ustar00rootroot00000000000000 // A helper script to log a few times. We attempt to NOT emit // to stdout or stderr because this is used for dtrace testing // and we don't want to mix output. var bunyan = require('../lib/bunyan'); var log = bunyan.createLogger({ name: 'play', serializers: bunyan.stdSerializers }); log.debug({foo: 'bar'}, 'hi at debug') log.trace('hi at trace') node-bunyan-2.0.5/test/log.test.js000066400000000000000000000175501377616564400170440ustar00rootroot00000000000000/* * Copyright 2020 Trent Mick * * Test the `log.trace(...)`, `log.debug(...)`, ..., `log.fatal(...)` API. */ var util = require('util'), format = util.format, inspect = util.inspect; var test = require('tap').test; var p = console.log; var bunyan = require('../lib/bunyan'); // ---- test boolean `log.()` calls var log1 = bunyan.createLogger({ name: 'log1', streams: [ { path: __dirname + '/log.test.log1.log', level: 'info' } ] }); var log2 = bunyan.createLogger({ name: 'log2', streams: [ { path: __dirname + '/log.test.log2a.log', level: 'error' }, { path: __dirname + '/log.test.log2b.log', level: 'debug' } ] }) test('log.LEVEL() -> boolean', function (t) { t.equal(log1.trace(), false, 'log1.trace() is false') t.equal(log1.debug(), false) t.equal(log1.info(), true) t.equal(log1.warn(), true) t.equal(log1.error(), true) t.equal(log1.fatal(), true) // Level is the *lowest* level of all streams. t.equal(log2.trace(), false) t.equal(log2.debug(), true) t.equal(log2.info(), true) t.equal(log2.warn(), true) t.equal(log2.error(), true) t.equal(log2.fatal(), true) t.end(); }); // ---- test `log.(...)` calls which various input types function Catcher() { this.records = []; } Catcher.prototype.write = function (record) { this.records.push(record); } var catcher = new Catcher(); var log3 = new bunyan.createLogger({ name: 'log3', streams: [ { type: 'raw', stream: catcher, level: 'trace' } ] }); var names = ['trace', 'debug', 'info', 'warn', 'error', 'fatal']; var fields = {one: 'un'}; test('log.info(undefined, )', function (t) { // https://github.com/nodejs/node/pull/23162 (starting in node v12) changed // util.format() handling such that this test case expected string differs. var expect; if (Number(process.versions.node.split('.')[0]) >= 12) { expect = 'undefined some message'; } else { expect = 'undefined \'some message\''; } names.forEach(function (lvl) { log3[lvl].call(log3, undefined, 'some message'); var rec = catcher.records[catcher.records.length - 1]; t.equal(rec.msg, expect, format('log.%s(undefined, "some message")', lvl)); }); t.end(); }); test('log.info(, undefined)', function (t) { names.forEach(function (lvl) { log3[lvl].call(log3, fields, undefined); var rec = catcher.records[catcher.records.length - 1]; t.equal(rec.msg, 'undefined', format('log.%s msg: expect "undefined", got %j', lvl, rec.msg)); t.equal(rec.one, 'un'); }); t.end(); }); test('log.info(null, )', function (t) { names.forEach(function (lvl) { log3[lvl].call(log3, null, 'some message'); var rec = catcher.records[catcher.records.length - 1]; t.equal(rec.msg, 'some message', format('log.%s msg is "some message"', lvl)); }); t.end(); }); test('log.info(, null)', function (t) { names.forEach(function (lvl) { log3[lvl].call(log3, fields, null); var rec = catcher.records[catcher.records.length - 1]; t.equal(rec.msg, 'null', format('log.%s msg: expect "null", got %j', lvl, rec.msg)); t.equal(rec.one, 'un'); }); t.end(); }); test('log.info()', function (t) { names.forEach(function (lvl) { log3[lvl].call(log3, 'some message'); var rec = catcher.records[catcher.records.length - 1]; t.equal(rec.msg, 'some message', format('log.%s msg is "some message"', lvl)); }); t.end(); }); test('log.info(, )', function (t) { names.forEach(function (lvl) { log3[lvl].call(log3, fields, 'some message'); var rec = catcher.records[catcher.records.length - 1]; t.equal(rec.msg, 'some message', format('log.%s msg: got %j', lvl, rec.msg)); t.equal(rec.one, 'un'); }); t.end(); }); test('log.info()', function (t) { names.forEach(function (lvl) { log3[lvl].call(log3, true); var rec = catcher.records[catcher.records.length - 1]; t.equal(rec.msg, 'true', format('log.%s msg is "true"', lvl)); }); t.end(); }); test('log.info(, )', function (t) { names.forEach(function (lvl) { log3[lvl].call(log3, fields, true); var rec = catcher.records[catcher.records.length - 1]; t.equal(rec.msg, 'true', format('log.%s msg: got %j', lvl, rec.msg)); t.equal(rec.one, 'un'); }); t.end(); }); test('log.info()', function (t) { names.forEach(function (lvl) { log3[lvl].call(log3, 3.14); var rec = catcher.records[catcher.records.length - 1]; t.equal(rec.msg, '3.14', format('log.%s msg: got %j', lvl, rec.msg)); }); t.end(); }); test('log.info(, )', function (t) { names.forEach(function (lvl) { log3[lvl].call(log3, fields, 3.14); var rec = catcher.records[catcher.records.length - 1]; t.equal(rec.msg, '3.14', format('log.%s msg: got %j', lvl, rec.msg)); t.equal(rec.one, 'un'); }); t.end(); }); test('log.info()', function (t) { var func = function func1() {}; names.forEach(function (lvl) { log3[lvl].call(log3, func); var rec = catcher.records[catcher.records.length - 1]; t.equal(rec.msg, '[Function: func1]', format('log.%s msg: got %j', lvl, rec.msg)); }); t.end(); }); test('log.info(, )', function (t) { var func = function func2() {}; names.forEach(function (lvl) { log3[lvl].call(log3, fields, func); var rec = catcher.records[catcher.records.length - 1]; t.equal(rec.msg, '[Function: func2]', format('log.%s msg: got %j', lvl, rec.msg)); t.equal(rec.one, 'un'); }); t.end(); }); test('log.info()', function (t) { var arr = ['a', 1, {two: 'deux'}]; names.forEach(function (lvl) { log3[lvl].call(log3, arr); var rec = catcher.records[catcher.records.length - 1]; t.equal(rec.msg, format(arr), format('log.%s msg: got %j', lvl, rec.msg)); }); t.end(); }); test('log.info(, )', function (t) { var arr = ['a', 1, {two: 'deux'}]; names.forEach(function (lvl) { log3[lvl].call(log3, fields, arr); var rec = catcher.records[catcher.records.length - 1]; t.equal(rec.msg, format(arr), format('log.%s msg: got %j', lvl, rec.msg)); t.equal(rec.one, 'un'); }); t.end(); }); /* * By accident (starting with trentm/node-bunyan#85 in bunyan@0.23.0), * log.info(null, ...) * was interpreted as `null` being the object of fields. It is gracefully * handled, which is good. However, had I to do it again, I would have made * that interpret `null` as the *message*, and no fields having been passed. * I think it is baked now. It would take a major bunyan rev to change it, * but I don't think it is worth it: passing `null` as the first arg isn't * really an intended way to call these Bunyan methods for either case. */ test('log.info(null)', function (t) { names.forEach(function (lvl) { log3[lvl].call(log3, null); var rec = catcher.records[catcher.records.length - 1]; t.equal(rec.msg, '', format('log.%s msg: got %j', lvl, rec.msg)); }); t.end(); }); test('log.info(null, )', function (t) { names.forEach(function (lvl) { log3[lvl].call(log3, null, 'my message'); var rec = catcher.records[catcher.records.length - 1]; t.equal(rec.msg, 'my message', format('log.%s msg: got %j', lvl, rec.msg)); }); t.end(); }); node-bunyan-2.0.5/test/other-api.test.js000066400000000000000000000023401377616564400201420ustar00rootroot00000000000000/* * Copyright 2020 Trent Mick * * Test other parts of the exported API. */ var test = require('tap').test; var bunyan = require('../lib/bunyan'); test('bunyan.s', function (t) { t.ok(bunyan.TRACE, 'TRACE'); t.ok(bunyan.DEBUG, 'DEBUG'); t.ok(bunyan.INFO, 'INFO'); t.ok(bunyan.WARN, 'WARN'); t.ok(bunyan.ERROR, 'ERROR'); t.ok(bunyan.FATAL, 'FATAL'); t.end(); }); test('bunyan.resolveLevel()', function (t) { t.equal(bunyan.resolveLevel('trace'), bunyan.TRACE, 'TRACE'); t.equal(bunyan.resolveLevel('TRACE'), bunyan.TRACE, 'TRACE'); t.equal(bunyan.resolveLevel('debug'), bunyan.DEBUG, 'DEBUG'); t.equal(bunyan.resolveLevel('DEBUG'), bunyan.DEBUG, 'DEBUG'); t.equal(bunyan.resolveLevel('info'), bunyan.INFO, 'INFO'); t.equal(bunyan.resolveLevel('INFO'), bunyan.INFO, 'INFO'); t.equal(bunyan.resolveLevel('warn'), bunyan.WARN, 'WARN'); t.equal(bunyan.resolveLevel('WARN'), bunyan.WARN, 'WARN'); t.equal(bunyan.resolveLevel('error'), bunyan.ERROR, 'ERROR'); t.equal(bunyan.resolveLevel('ERROR'), bunyan.ERROR, 'ERROR'); t.equal(bunyan.resolveLevel('fatal'), bunyan.FATAL, 'FATAL'); t.equal(bunyan.resolveLevel('FATAL'), bunyan.FATAL, 'FATAL'); t.end(); }); node-bunyan-2.0.5/test/process-exit.js000066400000000000000000000004031377616564400177170ustar00rootroot00000000000000var bunyan = require('../lib/bunyan'); var log = bunyan.createLogger({ name: 'default', streams: [ { type: 'rotating-file', path: __dirname + '/log.test.rot.log', period: '1d', count: 7 } ] }); console.log('done'); node-bunyan-2.0.5/test/process-exit.test.js000066400000000000000000000014451377616564400207040ustar00rootroot00000000000000'use strict'; /* * Test that bunyan process will terminate. * * Note: Currently (bunyan 0.23.1) this fails on node 0.8, because there is * no `unref` in node 0.8 and bunyan doesn't yet have `Logger.prototype.close()` * support. */ var exec = require('child_process').exec; var test = require('tap').test; var nodeVer = process.versions.node.split('.').map(Number); if (nodeVer[0] <= 0 && nodeVer[1] <= 8) { console.warn('skip test (node <= 0.8)'); } else { test('log with rotating file stream will terminate', function (t) { exec('node ' +__dirname + '/process-exit.js', {timeout: 1000}, function (err, stdout, stderr) { t.ifError(err); t.equal(stdout, 'done\n'); t.equal(stderr, ''); t.end(); }); }); } node-bunyan-2.0.5/test/raw-stream.test.js000066400000000000000000000052051377616564400203370ustar00rootroot00000000000000/* * Copyright 2020 Trent Mick * * Test `type: 'raw'` Logger streams. */ var format = require('util').format; var test = require('tap').test; var Logger = require('../lib/bunyan'); function CapturingStream(recs) { this.recs = recs; } CapturingStream.prototype.write = function (rec) { this.recs.push(rec); } test('raw stream', function (t) { var recs = []; var log = new Logger({ name: 'raw-stream-test', streams: [ { stream: new CapturingStream(recs), type: 'raw' } ] }); log.info('first'); log.info({two: 'deux'}, 'second'); t.equal(recs.length, 2); t.equal(typeof (recs[0]), 'object', 'first rec is an object'); t.equal(recs[1].two, 'deux', '"two" field made it through'); t.end(); }); test('raw streams and regular streams can mix', function (t) { var rawRecs = []; var nonRawRecs = []; var log = new Logger({ name: 'raw-stream-test', streams: [ { stream: new CapturingStream(rawRecs), type: 'raw' }, { stream: new CapturingStream(nonRawRecs) } ] }); log.info('first'); log.info({two: 'deux'}, 'second'); t.equal(rawRecs.length, 2); t.equal(typeof (rawRecs[0]), 'object', 'first rawRec is an object'); t.equal(rawRecs[1].two, 'deux', '"two" field made it through'); t.equal(nonRawRecs.length, 2); t.equal(typeof (nonRawRecs[0]), 'string', 'first nonRawRec is a string'); t.end(); }); test('child adding a non-raw stream works', function (t) { var parentRawRecs = []; var rawRecs = []; var nonRawRecs = []; var logParent = new Logger({ name: 'raw-stream-test', streams: [ { stream: new CapturingStream(parentRawRecs), type: 'raw' } ] }); var logChild = logParent.child({ child: true, streams: [ { stream: new CapturingStream(rawRecs), type: 'raw' }, { stream: new CapturingStream(nonRawRecs) } ] }); logParent.info('first'); logChild.info({two: 'deux'}, 'second'); t.equal(rawRecs.length, 1, format('rawRecs length should be 1 (is %d)', rawRecs.length)); t.equal(typeof (rawRecs[0]), 'object', 'rawRec entry is an object'); t.equal(rawRecs[0].two, 'deux', '"two" field made it through'); t.equal(nonRawRecs.length, 1); t.equal(typeof (nonRawRecs[0]), 'string', 'first nonRawRec is a string'); t.end(); }); node-bunyan-2.0.5/test/ringbuffer.test.js000066400000000000000000000020451377616564400204050ustar00rootroot00000000000000/* * Test the RingBuffer output stream. */ var test = require('tap').test; var Logger = require('../lib/bunyan'); var ringbuffer = new Logger.RingBuffer({ 'limit': 5 }); var log1 = new Logger({ name: 'log1', streams: [ { stream: ringbuffer, type: 'raw', level: 'info' } ] }); test('ringbuffer', function (t) { log1.info('hello'); log1.trace('there'); log1.error('android'); t.equal(ringbuffer.records.length, 2); t.equal(ringbuffer.records[0]['msg'], 'hello'); t.equal(ringbuffer.records[1]['msg'], 'android'); log1.error('one'); log1.error('two'); log1.error('three'); t.equal(ringbuffer.records.length, 5); log1.error('four'); t.equal(ringbuffer.records.length, 5); t.equal(ringbuffer.records[0]['msg'], 'android'); t.equal(ringbuffer.records[1]['msg'], 'one'); t.equal(ringbuffer.records[2]['msg'], 'two'); t.equal(ringbuffer.records[3]['msg'], 'three'); t.equal(ringbuffer.records[4]['msg'], 'four'); t.end(); }); node-bunyan-2.0.5/test/safe-json-stringify-1.js000066400000000000000000000004131377616564400213320ustar00rootroot00000000000000var bunyan = require('../lib/bunyan'); var log = bunyan.createLogger({ name: 'safe-json-stringify-1' }); var obj = {}; obj.__defineGetter__('boom', function () { throw new Error('__defineGetter__ ouch!'); }); log.info({obj: obj}, 'using __defineGetter__'); node-bunyan-2.0.5/test/safe-json-stringify-2.js000066400000000000000000000005011377616564400213310ustar00rootroot00000000000000process.env.BUNYAN_TEST_NO_SAFE_JSON_STRINGIFY = '1'; var bunyan = require('../lib/bunyan'); var log = bunyan.createLogger({ name: 'safe-json-stringify-2' }); var obj = {}; obj.__defineGetter__('boom', function () { throw new Error('__defineGetter__ ouch!'); }); log.info({obj: obj}, 'using __defineGetter__'); node-bunyan-2.0.5/test/safe-json-stringify-3.js000066400000000000000000000006761377616564400213470ustar00rootroot00000000000000var bunyan = require('../lib/bunyan'); var log = bunyan.createLogger({ name: 'safe-json-stringify-3' }); // And using `Object.defineProperty`. var obj = {}; Object.defineProperty(obj, 'boom', { get: function () { throw new Error('defineProperty ouch!'); }, enumerable: true // enumerable is false by default }); // Twice to test the 'warnKey' usage. for (var i = 0; i < 2; i++) { log.info({obj: obj}, 'using defineProperty'); } node-bunyan-2.0.5/test/safe-json-stringify-4.js000066400000000000000000000007641377616564400213460ustar00rootroot00000000000000process.env.BUNYAN_TEST_NO_SAFE_JSON_STRINGIFY = '1'; var bunyan = require('../lib/bunyan'); var log = bunyan.createLogger({ name: 'safe-json-stringify-4' }); // And using `Object.defineProperty`. var obj = {}; Object.defineProperty(obj, 'boom', { get: function () { throw new Error('defineProperty ouch!'); }, enumerable: true // enumerable is false by default }); // Twice to test the 'warnKey' usage. for (var i = 0; i < 2; i++) { log.info({obj: obj}, 'using defineProperty'); } node-bunyan-2.0.5/test/safe-json-stringify.test.js000066400000000000000000000041461377616564400221610ustar00rootroot00000000000000/* * Copyright 2020 Trent Mick * * If available, use `safe-json-stringfy` as a fallback stringifier. * This covers the case where an enumerable property throws an error * in its getter. * * See */ var exec = require('child_process').exec; var test = require('tap').test; test('__defineGetter__ boom', function (t) { var cmd = process.execPath + ' ' + __dirname + '/safe-json-stringify-1.js'; exec(cmd, function (err, stdout, stderr) { t.ifError(err, err); var rec = JSON.parse(stdout.trim()); t.equal(rec.obj.boom, '[Throws: __defineGetter__ ouch!]'); t.end(); }); }); test('__defineGetter__ boom, without safe-json-stringify', function (t) { var cmd = process.execPath + ' ' + __dirname + '/safe-json-stringify-2.js'; exec(cmd, function (err, stdout, stderr) { t.ifError(err, err); t.ok(stdout.indexOf('Exception in JSON.stringify') !== -1); t.ok(stderr.indexOf( 'You can install the "safe-json-stringify" module') !== -1); t.end(); }); }); test('defineProperty boom', function (t) { var cmd = process.execPath + ' ' + __dirname + '/safe-json-stringify-3.js'; exec(cmd, function (err, stdout, stderr) { t.ifError(err, err); var recs = stdout.trim().split(/\n/g); t.equal(recs.length, 2); var rec = JSON.parse(recs[0]); t.equal(rec.obj.boom, '[Throws: defineProperty ouch!]'); t.end(); }); }); test('defineProperty boom, without safe-json-stringify', function (t) { var cmd = process.execPath + ' ' + __dirname + '/safe-json-stringify-4.js'; exec(cmd, function (err, stdout, stderr) { t.ifError(err, err); t.ok(stdout.indexOf('Exception in JSON.stringify') !== -1); t.equal(stdout.match(/Exception in JSON.stringify/g).length, 2); t.ok(stderr.indexOf( 'You can install the "safe-json-stringify" module') !== -1); t.equal(stderr.match( /* JSSTYLED */ /You can install the "safe-json-stringify" module/g).length, 1); t.end(); }); }); node-bunyan-2.0.5/test/serializers.test.js000066400000000000000000000240101377616564400206040ustar00rootroot00000000000000/* * Copyright 2020 Trent Mick * * Test the standard serializers in Bunyan. */ var http = require('http'); var test = require('tap').test; var verror = require('verror'); var bunyan = require('../lib/bunyan'); function CapturingStream(recs) { this.recs = recs; } CapturingStream.prototype.write = function (rec) { this.recs.push(rec); } function createLogger(serializers, records) { return bunyan.createLogger({ name: 'serializer-test', streams: [ { stream: new CapturingStream(records), type: 'raw' } ], serializers: serializers }); } test('req serializer', function (t) { var records = []; var log = createLogger({ req: bunyan.stdSerializers.req }, records); // None of these should blow up. var bogusReqs = [ undefined, null, {}, 1, 'string', [1, 2, 3], {'foo':'bar'} ]; for (var i = 0; i < bogusReqs.length; i++) { log.info({req: bogusReqs[i]}, 'hi'); t.equal(records[i].req, bogusReqs[i]); } // Get http request and response objects to play with and test. var theReq, theRes; var server = http.createServer(function (req, res) { theReq = req; theRes = res; res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }) server.listen(8765, function () { http.get({host: '127.0.0.1', port: 8765, path: '/'}, function (res) { res.resume(); log.info({req: theReq}, 'the request'); var lastRecord = records[records.length-1]; t.equal(lastRecord.req.method, 'GET'); t.equal(lastRecord.req.url, theReq.url); t.equal(lastRecord.req.remoteAddress, theReq.connection.remoteAddress); t.equal(lastRecord.req.remotePort, theReq.connection.remotePort); server.close(); t.end(); }).on('error', function (err) { t.ok(false, 'error requesting to our test server: ' + err); server.close(); t.end(); }); }); }); test('req serializer - express.originalUrl', function (t) { var records = []; var log = createLogger({ req: bunyan.stdSerializers.req }, records); // Get http request and response objects to play with and test. var theReq, theRes; var server = http.createServer(function (req, res) { theReq = req; theRes = res; req.originalUrl = '/original-url' + req.url; res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }) server.listen(8765, function () { http.get({host: '127.0.0.1', port: 8765, path: '/'}, function (res) { res.resume(); log.info({req: theReq}, 'the request'); var lastRecord = records[records.length-1]; t.equal(lastRecord.req.method, 'GET'); t.equal(lastRecord.req.url, '/original-url' + theReq.url); t.equal(lastRecord.req.remoteAddress, theReq.connection.remoteAddress); t.equal(lastRecord.req.remotePort, theReq.connection.remotePort); server.close(); t.end(); }).on('error', function (err) { t.ok(false, 'error requesting to our test server: ' + err); server.close(); t.end(); }); }); }); test('res serializer', function (t) { var records = []; var log = createLogger({ res: bunyan.stdSerializers.res }, records); // None of these should blow up. var bogusRess = [ undefined, null, {}, 1, 'string', [1, 2, 3], {'foo':'bar'} ]; for (var i = 0; i < bogusRess.length; i++) { log.info({res: bogusRess[i]}, 'hi'); t.equal(records[i].res, bogusRess[i]); } // Get http request and response objects to play with and test. var theReq, theRes; var server = http.createServer(function (req, res) { theReq = req; theRes = res; res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }) server.listen(8765, function () { http.get({host: '127.0.0.1', port: 8765, path: '/'}, function (res) { res.resume(); log.info({res: theRes}, 'the response'); var lastRecord = records[records.length-1]; t.equal(lastRecord.res.statusCode, theRes.statusCode); t.equal(lastRecord.res.header, theRes._header); server.close(); t.end(); }).on('error', function (err) { t.ok(false, 'error requesting to our test server: ' + err); server.close(); t.end(); }); }); }); test('err serializer', function (t) { var records = []; var log = createLogger({ err: bunyan.stdSerializers.err }, records); // None of these should blow up. var bogusErrs = [ undefined, null, {}, 1, 'string', [1, 2, 3], {'foo':'bar'} ]; for (var i = 0; i < bogusErrs.length; i++) { log.info({err: bogusErrs[i]}, 'hi'); t.equal(records[i].err, bogusErrs[i]); } var theErr = new TypeError('blah'); log.info(theErr, 'the error'); var lastRecord = records[records.length-1]; t.equal(lastRecord.err.message, theErr.message); t.equal(lastRecord.err.name, theErr.name); t.equal(lastRecord.err.stack, theErr.stack); t.end(); }); test('err serializer: custom serializer', function (t) { var records = []; function customSerializer(err) { return { message: err.message, name: err.name, stack: err.stack, beep: err.beep }; } var log = createLogger({ err: customSerializer }, records); var e1 = new Error('message1'); e1.beep = 'bop'; var e2 = new Error('message2'); var errs = [e1, e2]; for (var i = 0; i < errs.length; i++) { log.info(errs[i]); t.equal(records[i].err.message, errs[i].message); t.equal(records[i].err.beep, errs[i].beep); } t.end(); }); test('err serializer: long stack', function (t) { var records = []; var log = createLogger({ err: bunyan.stdSerializers.err }, records); var topErr, midErr, bottomErr; // Just a VError. topErr = new verror.VError('top err'); log.info(topErr, 'the error'); var lastRecord = records[records.length-1]; t.equal(lastRecord.err.message, topErr.message, 'Just a VError'); t.equal(lastRecord.err.name, topErr.name, 'Just a VError'); t.equal(lastRecord.err.stack, topErr.stack, 'Just a VError'); // Just a WError. topErr = new verror.WError('top err'); log.info(topErr, 'the error'); var lastRecord = records[records.length-1]; t.equal(lastRecord.err.message, topErr.message, 'Just a WError'); t.equal(lastRecord.err.name, topErr.name, 'Just a WError'); t.equal(lastRecord.err.stack, topErr.stack, 'Just a WError'); // WError <- TypeError bottomErr = new TypeError('bottom err'); topErr = new verror.WError(bottomErr, 'top err'); log.info(topErr, 'the error'); var lastRecord = records[records.length-1]; t.equal(lastRecord.err.message, topErr.message, 'WError <- TypeError'); t.equal(lastRecord.err.name, topErr.name, 'WError <- TypeError'); var expectedStack = topErr.stack + '\nCaused by: ' + bottomErr.stack; t.equal(lastRecord.err.stack, expectedStack, 'WError <- TypeError'); // WError <- WError bottomErr = new verror.WError('bottom err'); topErr = new verror.WError(bottomErr, 'top err'); log.info(topErr, 'the error'); var lastRecord = records[records.length-1]; t.equal(lastRecord.err.message, topErr.message, 'WError <- WError'); t.equal(lastRecord.err.name, topErr.name, 'WError <- WError'); var expectedStack = topErr.stack + '\nCaused by: ' + bottomErr.stack; t.equal(lastRecord.err.stack, expectedStack, 'WError <- WError'); // WError <- WError <- TypeError bottomErr = new TypeError('bottom err'); midErr = new verror.WError(bottomErr, 'mid err'); topErr = new verror.WError(midErr, 'top err'); log.info(topErr, 'the error'); var lastRecord = records[records.length-1]; t.equal(lastRecord.err.message, topErr.message, 'WError <- WError <- TypeError'); t.equal(lastRecord.err.name, topErr.name, 'WError <- WError <- TypeError'); var expectedStack = (topErr.stack + '\nCaused by: ' + midErr.stack + '\nCaused by: ' + bottomErr.stack); t.equal(lastRecord.err.stack, expectedStack, 'WError <- WError <- TypeError'); // WError <- WError <- WError bottomErr = new verror.WError('bottom err'); midErr = new verror.WError(bottomErr, 'mid err'); topErr = new verror.WError(midErr, 'top err'); log.info(topErr, 'the error'); var lastRecord = records[records.length-1]; t.equal(lastRecord.err.message, topErr.message, 'WError <- WError <- WError'); t.equal(lastRecord.err.name, topErr.name, 'WError <- WError <- WError'); var expectedStack = (topErr.stack + '\nCaused by: ' + midErr.stack + '\nCaused by: ' + bottomErr.stack); t.equal(lastRecord.err.stack, expectedStack, 'WError <- WError <- WError'); t.end(); }); // Bunyan 0.18.3 introduced a bug where *all* serializers are applied // even if the log record doesn't have the associated key. That means // serializers that don't handle an `undefined` value will blow up. test('do not apply serializers if no record key', function (t) { var records = []; var log = createLogger({ err: bunyan.stdSerializers.err, boom: function (value) { throw new Error('boom'); } }, records ); log.info({foo: 'bar'}, 'record one'); log.info({err: new Error('record two err')}, 'record two'); t.equal(records[0].boom, undefined); t.equal(records[0].foo, 'bar'); t.equal(records[1].boom, undefined); t.equal(records[1].err.message, 'record two err'); t.end(); }); node-bunyan-2.0.5/test/src.test.js000066400000000000000000000021501377616564400170400ustar00rootroot00000000000000/* * Copyright 2020 Trent Mick. * * Test `src: true` usage. */ // Intentionally on line 8 for tests below: function logSomething(log) { log.info('something'); } var test = require('tap').test; var format = require('util').format; var Logger = require('../lib/bunyan'); function CapturingStream(recs) { this.recs = recs; } CapturingStream.prototype.write = function (rec) { this.recs.push(rec); } test('src', function (t) { var recs = []; var log = new Logger({ name: 'src-test', src: true, streams: [ { stream: new CapturingStream(recs), type: 'raw' } ] }); log.info('top-level'); logSomething(log); t.equal(recs.length, 2); recs.forEach(function (rec) { t.ok(rec.src); t.equal(typeof (rec.src), 'object'); t.equal(rec.src.file, __filename); t.ok(rec.src.line); t.equal(typeof (rec.src.line), 'number'); }); var rec = recs[1]; t.ok(rec.src.func); t.equal(rec.src.func, 'logSomething'); t.equal(rec.src.line, 8); t.end(); }); node-bunyan-2.0.5/test/stream-levels.test.js000066400000000000000000000101321377616564400210330ustar00rootroot00000000000000/* * Copyright 2020 Trent Mick * * Test that streams (the various way they can be added to * a Logger instance) get the appropriate level. */ var util = require('util'), format = util.format, inspect = util.inspect; var test = require('tap').test; var bunyan = require('../lib/bunyan'); // ---- Tests var log1 = bunyan.createLogger({ name: 'log1', streams: [ { path: __dirname + '/level.test.log1.log', level: 'info' } ] }); test('default stream log level', function (t) { var log = bunyan.createLogger({ name: 'foo' }); t.equal(log.level(), bunyan.INFO); t.equal(log.streams[0].level, bunyan.INFO); t.end(); }); test('default stream, level=DEBUG specified', function (t) { var log = bunyan.createLogger({ name: 'foo', level: bunyan.DEBUG }); t.equal(log.level(), bunyan.DEBUG); t.equal(log.streams[0].level, bunyan.DEBUG); t.end(); }); test('default stream, level="trace" specified', function (t) { var log = bunyan.createLogger({ name: 'foo', level: 'trace' }); t.equal(log.level(), bunyan.TRACE); t.equal(log.streams[0].level, bunyan.TRACE); t.end(); }); test('stream & level="trace" specified', function (t) { var log = bunyan.createLogger({ name: 'foo', stream: process.stderr, level: 'trace' }); t.equal(log.level(), bunyan.TRACE); t.equal(log.streams[0].level, bunyan.TRACE); t.end(); }); test('one stream, default level', function (t) { var log = bunyan.createLogger({ name: 'foo', streams: [ { stream: process.stderr } ] }); t.equal(log.level(), bunyan.INFO); t.equal(log.streams[0].level, bunyan.INFO); t.end(); }); test('one stream, top-"level" specified', function (t) { var log = bunyan.createLogger({ name: 'foo', level: 'error', streams: [ { stream: process.stderr } ] }); t.equal(log.level(), bunyan.ERROR); t.equal(log.streams[0].level, bunyan.ERROR); t.end(); }); test('one stream, stream-"level" specified', function (t) { var log = bunyan.createLogger({ name: 'foo', streams: [ { stream: process.stderr, level: 'error' } ] }); t.equal(log.level(), bunyan.ERROR); t.equal(log.streams[0].level, bunyan.ERROR); t.end(); }); test('one stream, both-"level" specified', function (t) { var log = bunyan.createLogger({ name: 'foo', level: 'debug', streams: [ { stream: process.stderr, level: 'error' } ] }); t.equal(log.level(), bunyan.ERROR); t.equal(log.streams[0].level, bunyan.ERROR); t.end(); }); test('two streams, both-"level" specified', function (t) { var log = bunyan.createLogger({ name: 'foo', level: 'debug', streams: [ { stream: process.stdout, level: 'trace' }, { stream: process.stderr, level: 'fatal' } ] }); t.equal(log.level(), bunyan.TRACE, 'log.level()'); t.equal(log.streams[0].level, bunyan.TRACE); t.equal(log.streams[1].level, bunyan.FATAL); t.end(); }); test('two streams, one with "level" specified', function (t) { var log = bunyan.createLogger({ name: 'foo', streams: [ { stream: process.stdout, }, { stream: process.stderr, level: 'fatal' } ] }); t.equal(log.level(), bunyan.INFO); t.equal(log.streams[0].level, bunyan.INFO); t.equal(log.streams[1].level, bunyan.FATAL); t.end(); }); // Issue #335 test('log level 0 to turn on all logging', function (t) { var log = bunyan.createLogger({ name: 'foo', level: 0 }); t.equal(log.level(), 0); t.equal(log.streams[0].level, 0); t.end(); }); node-bunyan-2.0.5/tools/000077500000000000000000000000001377616564400151205ustar00rootroot00000000000000node-bunyan-2.0.5/tools/colors.log000066400000000000000000000036451377616564400171340ustar00rootroot00000000000000# A log file to use to show bunyan colors {"name":"colors","hostname":"grape.local","pid":52694,"level":10,"msg":"this is the msg","time":"2014-08-10T07:02:40.552Z","src":{"file":"/Users/trentm/tm/node-bunyan/examples/src.js","line":8},"v":0} {"name":"colors","hostname":"grape.local","pid":52694,"level":20,"msg":"this is the msg","time":"2014-08-10T07:02:40.550Z","v":0} {"name":"colors","hostname":"grape.local","pid":52694,"level":30,"msg":"this is the msg","time":"2014-08-10T07:02:40.551Z","v":0} {"name":"colors","hostname":"grape.local","pid":52694,"level":40,"msg":"this is the msg","time":"2014-08-10T07:02:40.551Z","v":0} {"name":"colors","hostname":"grape.local","pid":52694,"level":50,"msg":"this is the msg","time":"2014-08-10T07:02:40.551Z","v":0} {"name":"colors","hostname":"grape.local","pid":52694,"agent":"a6359483-80b7-4a71-bb12-84cab83816b3","level":30,"req_id":"baf281a7-e4f3-4e6c-80d3-8154b5b220d0","req":{"method":"HEAD","url":"/agentprobes","headers":{"accept":"application/json","content-type":"application/json","host":"localhost","connection":"close"},"httpVersion":"1.1","trailers":{},"version":"*"},"res":{"statusCode":200,"headers":{"content-md5":"11FxOYiYfpMxmANj4kGJzg==","access-control-allow-origin":"*","access-control-allow-headers":"Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version","access-control-allow-methods":"HEAD","access-control-expose-headers":"X-Api-Version, X-Request-Id, X-Response-Time","connection":"close","date":"Fri, 14 Dec 2012 01:11:04 GMT","server":"Amon Relay/1.0.0","x-request-id":"baf281a7-e4f3-4e6c-80d3-8154b5b220d0","x-response-time":0},"trailer":false},"route":{"name":"HeadAgentProbes","version":false},"latency":0,"secure":false,"_audit":true,"msg":"HeadAgentProbes handled: 200","time":"2012-12-14T01:11:04.218Z","v":0} {"name":"colors","hostname":"grape.local","pid":52694,"level":60,"msg":"this is the msg","time":"2014-08-10T07:02:40.551Z","v":0} node-bunyan-2.0.5/tools/cutarelease.py000077500000000000000000000531171377616564400200010ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (c) 2009-2012 Trent Mick """cutarelease -- Cut a release of your project. A script that will help cut a release for a git-based project that follows a few conventions. It'll update your changelog (CHANGES.md), add a git tag, push those changes, update your version to the next patch level release and create a new changelog section for that new version. Conventions: - XXX """ __version_info__ = (1, 0, 7) __version__ = '.'.join(map(str, __version_info__)) import sys import os from os.path import join, dirname, normpath, abspath, exists, basename, splitext from glob import glob from pprint import pprint import re import codecs import logging import optparse import json import time #---- globals and config log = logging.getLogger("cutarelease") class Error(Exception): pass #---- main functionality def cutarelease(project_name, version_files, dry_run=False): """Cut a release. @param project_name {str} @param version_files {list} List of paths to files holding the version info for this project. If none are given it attempts to guess the version file: package.json or VERSION.txt or VERSION or $project_name.py or lib/$project_name.py or $project_name.js or lib/$project_name.js. The version file can be in one of the following forms: - A .py file, in which case the file is expect to have a top-level global called "__version_info__" as follows. [1] __version_info__ = (0, 7, 6) Note that I typically follow that with the following to get a string version attribute on my modules: __version__ = '.'.join(map(str, __version_info__)) - A .js file, in which case the file is expected to have a top-level global called "VERSION" as follows: ver VERSION = "1.2.3"; - A "package.json" file, typical of a node.js npm-using project. The package.json file must have a "version" field. - TODO: A simple version file whose only content is a "1.2.3"-style version string. [1]: This is a convention I tend to follow in my projects. Granted it might not be your cup of tea. I should add support for just `__version__ = "1.2.3"`. I'm open to other suggestions too. """ dry_run_str = dry_run and " (dry-run)" or "" if not version_files: log.info("guessing version file") candidates = [ "package.json", "VERSION.txt", "VERSION", "%s.py" % project_name, "lib/%s.py" % project_name, "%s.js" % project_name, "lib/%s.js" % project_name, ] for candidate in candidates: if exists(candidate): version_files = [candidate] break else: raise Error("could not find a version file: specify its path or " "add one of the following to your project: '%s'" % "', '".join(candidates)) log.info("using '%s' as version file", version_files[0]) parsed_version_files = [_parse_version_file(f) for f in version_files] version_file_type, version_info = parsed_version_files[0] version = _version_from_version_info(version_info) # Confirm if not dry_run: answer = query_yes_no("* * *\n" "Are you sure you want cut a %s release?\n" "This will involved commits and a push." % version, default="no") print "* * *" if answer != "yes": log.info("user abort") return log.info("cutting a %s release%s", version, dry_run_str) # Checks: Ensure there is a section in changes for this version. changes_path = "CHANGES.md" changes_txt, changes, nyr = parse_changelog(changes_path) #pprint(changes) top_ver = changes[0]["version"] if top_ver != version: raise Error("changelog '%s' top section says " "version %r, expected version %r: aborting" % (changes_path, top_ver, version)) top_verline = changes[0]["verline"] if not top_verline.endswith(nyr): answer = query_yes_no("\n* * *\n" "The changelog '%s' top section doesn't have the expected\n" "'%s' marker. Has this been released already?" % (changes_path, nyr), default="yes") print "* * *" if answer != "no": log.info("abort") return top_body = changes[0]["body"] if top_body.strip() == "(nothing yet)": raise Error("top section body is `(nothing yet)': it looks like " "nothing has been added to this release") # Commits to prepare release. changes_txt_before = changes_txt changes_txt = changes_txt.replace(" (not yet released)", "", 1) if not dry_run and changes_txt != changes_txt_before: log.info("prepare `%s' for release", changes_path) f = codecs.open(changes_path, 'w', 'utf-8') f.write(changes_txt) f.close() run('git commit %s -m "%s"' % (changes_path, version)) # Tag version and push. curr_tags = set(t for t in _capture_stdout(["git", "tag", "-l"]).split('\n') if t) if not dry_run and version not in curr_tags: log.info("tag the release") date = time.strftime("%Y-%m-%d") run('git tag -a "%s" -m "version %s (%s)"' % (version, version, date)) run('git push --tags') # Optionally release. if exists("package.json"): answer = query_yes_no("\n* * *\nPublish to npm?", default="yes") print "* * *" if answer == "yes": if dry_run: log.info("skipping npm publish (dry-run)") else: run('npm publish') elif exists("setup.py"): answer = query_yes_no("\n* * *\nPublish to pypi?", default="yes") print "* * *" if answer == "yes": if dry_run: log.info("skipping pypi publish (dry-run)") else: run("%spython setup.py sdist --formats zip upload" % _setup_command_prefix()) # Commits to prepare for future dev and push. # - update changelog file next_version_info = _get_next_version_info(version_info) next_version = _version_from_version_info(next_version_info) log.info("prepare for future dev (version %s)", next_version) marker = "## " + changes[0]["verline"] if marker.endswith(nyr): marker = marker[0:-len(nyr)] if marker not in changes_txt: raise Error("couldn't find `%s' marker in `%s' " "content: can't prep for subsequent dev" % (marker, changes_path)) next_verline = "%s %s%s" % (marker.rsplit(None, 1)[0], next_version, nyr) changes_txt = changes_txt.replace(marker + '\n', "%s\n\n(nothing yet)\n\n\n%s\n" % (next_verline, marker)) if not dry_run: f = codecs.open(changes_path, 'w', 'utf-8') f.write(changes_txt) f.close() # - update version file next_version_tuple = _tuple_from_version(next_version) for i, ver_file in enumerate(version_files): ver_content = codecs.open(ver_file, 'r', 'utf-8').read() ver_file_type, ver_info = parsed_version_files[i] if ver_file_type == "json": marker = '"version": "%s"' % version if marker not in ver_content: raise Error("couldn't find `%s' version marker in `%s' " "content: can't prep for subsequent dev" % (marker, ver_file)) ver_content = ver_content.replace(marker, '"version": "%s"' % next_version) elif ver_file_type == "javascript": candidates = [ ("single", "var VERSION = '%s';" % version), ("double", 'var VERSION = "%s";' % version), ] for quote_type, marker in candidates: if marker in ver_content: break else: raise Error("couldn't find any candidate version marker in " "`%s' content: can't prep for subsequent dev: %r" % (ver_file, candidates)) if quote_type == "single": ver_content = ver_content.replace(marker, "var VERSION = '%s';" % next_version) else: ver_content = ver_content.replace(marker, 'var VERSION = "%s";' % next_version) elif ver_file_type == "python": marker = "__version_info__ = %r" % (version_info,) if marker not in ver_content: raise Error("couldn't find `%s' version marker in `%s' " "content: can't prep for subsequent dev" % (marker, ver_file)) ver_content = ver_content.replace(marker, "__version_info__ = %r" % (next_version_tuple,)) elif ver_file_type == "version": ver_content = next_version else: raise Error("unknown ver_file_type: %r" % ver_file_type) if not dry_run: log.info("update version to '%s' in '%s'", next_version, ver_file) f = codecs.open(ver_file, 'w', 'utf-8') f.write(ver_content) f.close() if not dry_run: run('git commit %s %s -m "bumpver for subsequent work"' % ( changes_path, ' '.join(version_files))) run('git push') #---- internal support routines def _indent(s, indent=' '): return indent + indent.join(s.splitlines(True)) def _tuple_from_version(version): def _intify(s): try: return int(s) except ValueError: return s return tuple(_intify(b) for b in version.split('.')) def _get_next_version_info(version_info): next = list(version_info[:]) next[-1] += 1 return tuple(next) def _version_from_version_info(version_info): v = str(version_info[0]) state_dot_join = True for i in version_info[1:]: if state_dot_join: try: int(i) except ValueError: state_dot_join = False else: pass if state_dot_join: v += "." + str(i) else: v += str(i) return v _version_re = re.compile(r"^(\d+)\.(\d+)(?:\.(\d+)([abc](\d+)?)?)?$") def _version_info_from_version(version): m = _version_re.match(version) if not m: raise Error("could not convert '%s' version to version info" % version) version_info = [] for g in m.groups(): if g is None: break try: version_info.append(int(g)) except ValueError: version_info.append(g) return tuple(version_info) def _parse_version_file(version_file): """Get version info from the given file. It can be any of: Supported version file types (i.e. types of files from which we know how to parse the version string/number -- often by some convention): - json: use the "version" key - javascript: look for a `var VERSION = "1.2.3";` or `var VERSION = '1.2.3';` - python: Python script/module with `__version_info__ = (1, 2, 3)` - version: a VERSION.txt or VERSION file where the whole contents are the version string @param version_file {str} Can be a path or "type:path", where "type" is one of the supported types. """ # Get version file *type*. version_file_type = None match = re.compile("^([a-z]+):(.*)$").search(version_file) if match: version_file = match.group(2) version_file_type = match.group(1) aliases = { "js": "javascript" } if version_file_type in aliases: version_file_type = aliases[version_file_type] f = codecs.open(version_file, 'r', 'utf-8') content = f.read() f.close() if not version_file_type: # Guess the type. base = basename(version_file) ext = splitext(base)[1] if ext == ".json": version_file_type = "json" elif ext == ".py": version_file_type = "python" elif ext == ".js": version_file_type = "javascript" elif content.startswith("#!"): shebang = content.splitlines(False)[0] shebang_bits = re.split(r'[/ \t]', shebang) for name, typ in {"python": "python", "node": "javascript"}.items(): if name in shebang_bits: version_file_type = typ break elif base in ("VERSION", "VERSION.txt"): version_file_type = "version" if not version_file_type: raise RuntimeError("can't extract version from '%s': no idea " "what type of file it it" % version_file) if version_file_type == "json": obj = json.loads(content) version_info = _version_info_from_version(obj["version"]) elif version_file_type == "python": m = re.search(r'^__version_info__ = (.*?)$', content, re.M) version_info = eval(m.group(1)) elif version_file_type == "javascript": m = re.search(r'^var VERSION = (\'|")(.*?)\1;$', content, re.M) version_info = _version_info_from_version(m.group(2)) elif version_file_type == "version": version_info = _version_info_from_version(content.strip()) else: raise RuntimeError("unexpected version_file_type: %r" % version_file_type) return version_file_type, version_info def parse_changelog(changes_path): """Parse the given changelog path and return `(content, parsed, nyr)` where `nyr` is the ' (not yet released)' marker and `parsed` looks like: [{'body': u'\n(nothing yet)\n\n', 'verline': u'restify 1.0.1 (not yet released)', 'version': u'1.0.1'}, # version is parsed out for top section only {'body': u'...', 'verline': u'1.0.0'}, {'body': u'...', 'verline': u'1.0.0-rc2'}, {'body': u'...', 'verline': u'1.0.0-rc1'}] A changelog (CHANGES.md) is expected to look like this: # $project Changelog ## $next_version (not yet released) ... ## $version1 ... ## $version2 ... and so on The version lines are enforced as follows: - The top entry should have a " (not yet released)" suffix. "Should" because recovery from half-cutarelease failures is supported. - A version string must be extractable from there, but it tries to be loose (though strict "X.Y.Z" versioning is preferred). Allowed ## 1.0.0 ## my project 1.0.1 ## foo 1.2.3-rc2 Basically, (a) the " (not yet released)" is stripped, (b) the last token is the version, and (c) that version must start with a digit (sanity check). """ if not exists(changes_path): raise Error("changelog file '%s' not found" % changes_path) content = codecs.open(changes_path, 'r', 'utf-8').read() parser = re.compile( r'^##\s*(?P[^\n]*?)\s*$(?P.*?)(?=^##|\Z)', re.M | re.S) sections = parser.findall(content) # Sanity checks on changelog format. if not sections: template = "## 1.0.0 (not yet released)\n\n(nothing yet)\n" raise Error("changelog '%s' must have at least one section, " "suggestion:\n\n%s" % (changes_path, _indent(template))) first_section_verline = sections[0][0] nyr = ' (not yet released)' #if not first_section_verline.endswith(nyr): # eg = "## %s%s" % (first_section_verline, nyr) # raise Error("changelog '%s' top section must end with %r, " # "naive e.g.: '%s'" % (changes_path, nyr, eg)) items = [] for i, section in enumerate(sections): item = { "verline": section[0], "body": section[1] } if i == 0: # We only bother to pull out 'version' for the top section. verline = section[0] if verline.endswith(nyr): verline = verline[0:-len(nyr)] version = verline.split()[-1] try: int(version[0]) except ValueError: msg = '' if version.endswith(')'): msg = " (cutarelease is picky about the trailing %r " \ "on the top version line. Perhaps you misspelled " \ "that?)" % nyr raise Error("changelog '%s' top section version '%s' is " "invalid: first char isn't a number%s" % (changes_path, version, msg)) item["version"] = version items.append(item) return content, items, nyr ## {{{ http://code.activestate.com/recipes/577058/ (r2) def query_yes_no(question, default="yes"): """Ask a yes/no question via raw_input() and return their answer. "question" is a string that is presented to the user. "default" is the presumed answer if the user just hits . It must be "yes" (the default), "no" or None (meaning an answer is required of the user). The "answer" return value is one of "yes" or "no". """ valid = {"yes":"yes", "y":"yes", "ye":"yes", "no":"no", "n":"no"} if default == None: prompt = " [y/n] " elif default == "yes": prompt = " [Y/n] " elif default == "no": prompt = " [y/N] " else: raise ValueError("invalid default answer: '%s'" % default) while 1: sys.stdout.write(question + prompt) choice = raw_input().lower() if default is not None and choice == '': return default elif choice in valid.keys(): return valid[choice] else: sys.stdout.write("Please respond with 'yes' or 'no' "\ "(or 'y' or 'n').\n") ## end of http://code.activestate.com/recipes/577058/ }}} def _capture_stdout(argv): import subprocess p = subprocess.Popen(argv, stdout=subprocess.PIPE) return p.communicate()[0] class _NoReflowFormatter(optparse.IndentedHelpFormatter): """An optparse formatter that does NOT reflow the description.""" def format_description(self, description): return description or "" def run(cmd): """Run the given command. Raises OSError is the command returns a non-zero exit status. """ log.debug("running '%s'", cmd) fixed_cmd = cmd if sys.platform == "win32" and cmd.count('"') > 2: fixed_cmd = '"' + cmd + '"' retval = os.system(fixed_cmd) if hasattr(os, "WEXITSTATUS"): status = os.WEXITSTATUS(retval) else: status = retval if status: raise OSError(status, "error running '%s'" % cmd) def _setup_command_prefix(): prefix = "" if sys.platform == "darwin": # http://forums.macosxhints.com/archive/index.php/t-43243.html # This is an Apple customization to `tar` to avoid creating # '._foo' files for extended-attributes for archived files. prefix = "COPY_EXTENDED_ATTRIBUTES_DISABLE=1 " return prefix #---- mainline def main(argv): logging.basicConfig(format="%(name)s: %(levelname)s: %(message)s") log.setLevel(logging.INFO) # Parse options. parser = optparse.OptionParser(prog="cutarelease", usage='', version="%prog " + __version__, description=__doc__, formatter=_NoReflowFormatter()) parser.add_option("-v", "--verbose", dest="log_level", action="store_const", const=logging.DEBUG, help="more verbose output") parser.add_option("-q", "--quiet", dest="log_level", action="store_const", const=logging.WARNING, help="quieter output (just warnings and errors)") parser.set_default("log_level", logging.INFO) parser.add_option("--test", action="store_true", help="run self-test and exit (use 'eol.py -v --test' for verbose test output)") parser.add_option("-p", "--project-name", metavar="NAME", help='the name of this project (default is the base dir name)', default=basename(os.getcwd())) parser.add_option("-f", "--version-file", metavar="[TYPE:]PATH", action='append', dest="version_files", help='The path to the project file holding the version info. Can be ' 'specified multiple times if more than one file should be updated ' 'with new version info. If excluded, it will be guessed.') parser.add_option("-n", "--dry-run", action="store_true", help='Do a dry-run', default=False) opts, args = parser.parse_args() log.setLevel(opts.log_level) cutarelease(opts.project_name, opts.version_files, dry_run=opts.dry_run) ## {{{ http://code.activestate.com/recipes/577258/ (r5+) if __name__ == "__main__": try: retval = main(sys.argv) except KeyboardInterrupt: sys.exit(1) except SystemExit: raise except: import traceback, logging if not log.handlers and not logging.root.handlers: logging.basicConfig() skip_it = False exc_info = sys.exc_info() if hasattr(exc_info[0], "__name__"): exc_class, exc, tb = exc_info if isinstance(exc, IOError) and exc.args[0] == 32: # Skip 'IOError: [Errno 32] Broken pipe': often a cancelling of `less`. skip_it = True if not skip_it: tb_path, tb_lineno, tb_func = traceback.extract_tb(tb)[-1][:3] log.error("%s (%s:%s in %s)", exc_info[1], tb_path, tb_lineno, tb_func) else: # string exception log.error(exc_info[0]) if not skip_it: if log.isEnabledFor(logging.DEBUG): traceback.print_exception(*exc_info) sys.exit(1) else: sys.exit(retval) ## end of http://code.activestate.com/recipes/577258/ }}} node-bunyan-2.0.5/tools/jsstyle000077500000000000000000000605611377616564400165530ustar00rootroot00000000000000#!/usr/bin/env perl # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright 2011 Joyent, Inc. All rights reserved. # # jsstyle - check for some common stylistic errors. # # jsstyle is a sort of "lint" for Javascript coding style. This tool is # derived from the cstyle tool, used to check for the style used in the # Solaris kernel, sometimes known as "Bill Joy Normal Form". # # There's a lot this can't check for, like proper indentation of code # blocks. There's also a lot more this could check for. # # A note to the non perl literate: # # perl regular expressions are pretty much like egrep # regular expressions, with the following special symbols # # \s any space character # \S any non-space character # \w any "word" character [a-zA-Z0-9_] # \W any non-word character # \d a digit [0-9] # \D a non-digit # \b word boundary (between \w and \W) # \B non-word boundary # require 5.0; use IO::File; use Getopt::Std; use strict; my $usage = "Usage: jsstyle [-h?vcC] [-t ] [-f ] [-o ] file ... Check your JavaScript file for style. See for details on config options. Report bugs to . Options: -h print this help and exit -v verbose -c check continuation indentation inside functions -t specify tab width for line length calculation -C don't check anything in header block comments -f PATH path to a jsstyle config file -o OPTION1,OPTION2 set config options, e.g. '-o doxygen,indent=2' "; my %opts; if (!getopts("ch?o:t:f:vC", \%opts)) { print $usage; exit 2; } if (defined($opts{'h'}) || defined($opts{'?'})) { print $usage; exit; } my $check_continuation = $opts{'c'}; my $verbose = $opts{'v'}; my $ignore_hdr_comment = $opts{'C'}; my $tab_width = $opts{'t'}; # By default, tabs are 8 characters wide if (! defined($opts{'t'})) { $tab_width = 8; } # Load config my %config = ( indent => "tab", doxygen => 0, # doxygen comments: /** ... */ splint => 0, # splint comments. Needed? "unparenthesized-return" => 1, "literal-string-quote" => "single", # 'single' or 'double' "blank-after-start-comment" => 1, "continuation-at-front" => 0, "leading-right-paren-ok" => 0, "strict-indent" => 0 ); sub add_config_var ($$) { my ($scope, $str) = @_; if ($str !~ /^([\w-]+)(?:\s*=\s*(.*?))?$/) { die "$scope: invalid option: '$str'"; } my $name = $1; my $value = ($2 eq '' ? 1 : $2); #print "scope: '$scope', str: '$str', name: '$name', value: '$value'\n"; # Validate config var. if ($name eq "indent") { # A number of spaces or "tab". if ($value !~ /^\d+$/ && $value ne "tab") { die "$scope: invalid '$name': must be a number (of ". "spaces) or 'tab'"; } } elsif ($name eq "doxygen" || # boolean vars $name eq "splint" || $name eq "unparenthesized-return" || $name eq "continuation-at-front" || $name eq "leading-right-paren-ok" || $name eq "leading-comma-ok" || $name eq "uncuddled-else-ok" || $name eq "whitespace-after-left-paren-ok" || $name eq "strict-indent" || $name eq "blank-after-start-comment") { if ($value != 1 && $value != 0) { die "$scope: invalid '$name': don't give a value"; } } elsif ($name eq "literal-string-quote") { if ($value !~ /single|double/) { die "$scope: invalid '$name': must be 'single' ". "or 'double'"; } } else { die "$scope: unknown config var: $name"; } $config{$name} = $value; } if (defined($opts{'f'})) { my $path = $opts{'f'}; my $fh = new IO::File $path, "r"; if (!defined($fh)) { die "cannot open config path '$path'"; } my $line = 0; while (<$fh>) { $line++; s/^\s*//; # drop leading space s/\s*$//; # drop trailing space next if ! $_; # skip empty line next if /^#/; # skip comments add_config_var "$path:$line", $_; } } if (defined($opts{'o'})) { for my $x (split /,/, $opts{'o'}) { add_config_var "'-o' option", $x; } } my ($filename, $line, $prev); # shared globals my $fmt; my $hdr_comment_start; if ($verbose) { $fmt = "%s: %d: %s\n%s\n"; } else { $fmt = "%s: %d: %s\n"; } if ($config{"doxygen"}) { # doxygen comments look like "/*!" or "/**"; allow them. $hdr_comment_start = qr/^\s*\/\*[\!\*]?$/; } else { $hdr_comment_start = qr/^\s*\/\*$/; } # Note, following must be in single quotes so that \s and \w work right. my $lint_re = qr/\/\*(?: jsl:\w+?|ARGSUSED[0-9]*|NOTREACHED|LINTLIBRARY|VARARGS[0-9]*| CONSTCOND|CONSTANTCOND|CONSTANTCONDITION|EMPTY| FALLTHRU|FALLTHROUGH|LINTED.*?|PRINTFLIKE[0-9]*| PROTOLIB[0-9]*|SCANFLIKE[0-9]*|JSSTYLED.*? )\*\//x; my $splint_re = qr/\/\*@.*?@\*\//x; my $err_stat = 0; # exit status if ($#ARGV >= 0) { foreach my $arg (@ARGV) { my $fh = new IO::File $arg, "r"; if (!defined($fh)) { printf "%s: cannot open\n", $arg; } else { &jsstyle($arg, $fh); close $fh; } } } else { &jsstyle("", *STDIN); } exit $err_stat; my $no_errs = 0; # set for JSSTYLED-protected lines sub err($) { my ($error) = @_; unless ($no_errs) { printf $fmt, $filename, $., $error, $line; $err_stat = 1; } } sub err_prefix($$) { my ($prevline, $error) = @_; my $out = $prevline."\n".$line; unless ($no_errs) { printf $fmt, $filename, $., $error, $out; $err_stat = 1; } } sub err_prev($) { my ($error) = @_; unless ($no_errs) { printf $fmt, $filename, $. - 1, $error, $prev; $err_stat = 1; } } sub jsstyle($$) { my ($fn, $filehandle) = @_; $filename = $fn; # share it globally my $in_cpp = 0; my $next_in_cpp = 0; my $in_comment = 0; my $in_header_comment = 0; my $comment_done = 0; my $in_function = 0; my $in_function_header = 0; my $in_declaration = 0; my $note_level = 0; my $nextok = 0; my $nocheck = 0; my $in_string = 0; my ($okmsg, $comment_prefix); $line = ''; $prev = ''; reset_indent(); line: while (<$filehandle>) { s/\r?\n$//; # strip return and newline # save the original line, then remove all text from within # double or single quotes, we do not want to check such text. $line = $_; # # C allows strings to be continued with a backslash at the end of # the line. We translate that into a quoted string on the previous # line followed by an initial quote on the next line. # # (we assume that no-one will use backslash-continuation with character # constants) # $_ = '"' . $_ if ($in_string && !$nocheck && !$in_comment); # # normal strings and characters # s/'([^\\']|\\.)*'/\'\'/g; s/"([^\\"]|\\.)*"/\"\"/g; # # detect string continuation # if ($nocheck || $in_comment) { $in_string = 0; } else { # # Now that all full strings are replaced with "", we check # for unfinished strings continuing onto the next line. # $in_string = (s/([^"](?:"")*)"([^\\"]|\\.)*\\$/$1""/ || s/^("")*"([^\\"]|\\.)*\\$/""/); } # # figure out if we are in a cpp directive # $in_cpp = $next_in_cpp || /^\s*#/; # continued or started $next_in_cpp = $in_cpp && /\\$/; # only if continued # strip off trailing backslashes, which appear in long macros s/\s*\\$//; # an /* END JSSTYLED */ comment ends a no-check block. if ($nocheck) { if (/\/\* *END *JSSTYLED *\*\//) { $nocheck = 0; } else { reset_indent(); next line; } } # a /*JSSTYLED*/ comment indicates that the next line is ok. if ($nextok) { if ($okmsg) { err($okmsg); } $nextok = 0; $okmsg = 0; if (/\/\* *JSSTYLED.*\*\//) { /^.*\/\* *JSSTYLED *(.*) *\*\/.*$/; $okmsg = $1; $nextok = 1; } $no_errs = 1; } elsif ($no_errs) { $no_errs = 0; } # check length of line. # first, a quick check to see if there is any chance of being too long. if ((($line =~ tr/\t/\t/) * ($tab_width - 1)) + length($line) > 80) { # yes, there is a chance. # replace tabs with spaces and check again. my $eline = $line; 1 while $eline =~ s/\t+/' ' x (length($&) * $tab_width - length($`) % $tab_width)/e; if (length($eline) > 80) { err("line > 80 characters"); } } # ignore NOTE(...) annotations (assumes NOTE is on lines by itself). if ($note_level || /\b_?NOTE\s*\(/) { # if in NOTE or this is NOTE s/[^()]//g; # eliminate all non-parens $note_level += s/\(//g - length; # update paren nest level next; } # a /* BEGIN JSSTYLED */ comment starts a no-check block. if (/\/\* *BEGIN *JSSTYLED *\*\//) { $nocheck = 1; } # a /*JSSTYLED*/ comment indicates that the next line is ok. if (/\/\* *JSSTYLED.*\*\//) { /^.*\/\* *JSSTYLED *(.*) *\*\/.*$/; $okmsg = $1; $nextok = 1; } if (/\/\/ *JSSTYLED/) { /^.*\/\/ *JSSTYLED *(.*)$/; $okmsg = $1; $nextok = 1; } # universal checks; apply to everything if (/\t +\t/) { err("spaces between tabs"); } if (/ \t+ /) { err("tabs between spaces"); } if (/\s$/) { err("space or tab at end of line"); } if (/[^ \t(]\/\*/ && !/\w\(\/\*.*\*\/\);/) { err("comment preceded by non-blank"); } # is this the beginning or ending of a function? # (not if "struct foo\n{\n") if (/^{$/ && $prev =~ /\)\s*(const\s*)?(\/\*.*\*\/\s*)?\\?$/) { $in_function = 1; $in_declaration = 1; $in_function_header = 0; $prev = $line; next line; } if (/^}\s*(\/\*.*\*\/\s*)*$/) { if ($prev =~ /^\s*return\s*;/) { err_prev("unneeded return at end of function"); } $in_function = 0; reset_indent(); # we don't check between functions $prev = $line; next line; } if (/^\w*\($/) { $in_function_header = 1; } # a blank line terminates the declarations within a function. # XXX - but still a problem in sub-blocks. if ($in_declaration && /^$/) { $in_declaration = 0; } if ($comment_done) { $in_comment = 0; $in_header_comment = 0; $comment_done = 0; } # does this looks like the start of a block comment? if (/$hdr_comment_start/) { if ($config{"indent"} eq "tab") { if (!/^\t*\/\*/) { err("block comment not indented by tabs"); } } elsif (!/^ *\/\*/) { err("block comment not indented by spaces"); } $in_comment = 1; /^(\s*)\//; $comment_prefix = $1; if ($comment_prefix eq "") { $in_header_comment = 1; } $prev = $line; next line; } # are we still in the block comment? if ($in_comment) { if (/^$comment_prefix \*\/$/) { $comment_done = 1; } elsif (/\*\//) { $comment_done = 1; err("improper block comment close") unless ($ignore_hdr_comment && $in_header_comment); } elsif (!/^$comment_prefix \*[ \t]/ && !/^$comment_prefix \*$/) { err("improper block comment") unless ($ignore_hdr_comment && $in_header_comment); } } if ($in_header_comment && $ignore_hdr_comment) { $prev = $line; next line; } # check for errors that might occur in comments and in code. # allow spaces to be used to draw pictures in header comments. #if (/[^ ] / && !/".* .*"/ && !$in_header_comment) { # err("spaces instead of tabs"); #} #if (/^ / && !/^ \*[ \t\/]/ && !/^ \*$/ && # (!/^ \w/ || $in_function != 0)) { # err("indent by spaces instead of tabs"); #} if ($config{"indent"} eq "tab") { if (/^ {2,}/ && !/^ [^ ]/) { err("indent by spaces instead of tabs"); } } elsif (/^\t/) { err("indent by tabs instead of spaces") } elsif (/^( +)/ && !$in_comment) { my $indent = $1; if (length($indent) < $config{"indent"}) { err("indent of " . length($indent) . " space(s) instead of " . $config{"indent"}); } elsif ($config{"strict-indent"} && length($indent) % $config{"indent"} != 0) { err("indent is " . length($indent) . " not a multiple of " . $config{'indent'} . " spaces"); } } if (/^\t+ [^ \t\*]/ || /^\t+ \S/ || /^\t+ \S/) { err("continuation line not indented by 4 spaces"); } # A multi-line block comment must not have content on the first line. if (/^\s*\/\*./ && !/^\s*\/\*.*\*\// && !/$hdr_comment_start/) { err("improper first line of block comment"); } if ($in_comment) { # still in comment, don't do further checks $prev = $line; next line; } if ((/[^(]\/\*\S/ || /^\/\*\S/) && !(/$lint_re/ || ($config{"splint"} && /$splint_re/))) { err("missing blank after open comment"); } if (/\S\*\/[^)]|\S\*\/$/ && !(/$lint_re/ || ($config{"splint"} && /$splint_re/))) { err("missing blank before close comment"); } if ($config{"blank-after-start-comment"} && /(?\s][!<>=]=/ || /[^<>!=][!<>=]==?[^\s,=]/ || (/[^->]>[^,=>\s]/ && !/[^->]>$/) || (/[^<]<[^,=<\s]/ && !/[^<]<$/) || /[^<\s]<[^<]/ || /[^->\s]>[^>]/) { err("missing space around relational operator"); } if (/\S>>=/ || /\S<<=/ || />>=\S/ || /<<=\S/ || /\S[-+*\/&|^%]=/ || (/[^-+*\/&|^%!<>=\s]=[^=]/ && !/[^-+*\/&|^%!<>=\s]=$/) || (/[^!<>=]=[^=\s]/ && !/[^!<>=]=$/)) { # XXX - should only check this for C++ code # XXX - there are probably other forms that should be allowed if (!/\soperator=/) { err("missing space around assignment operator"); } } if (/[,;]\S/ && !/\bfor \(;;\)/ && # Allow a comma in a regex quantifier. !/\/.*?\{\d+,?\d*\}.*?\//) { err("comma or semicolon followed by non-blank"); } # check for commas preceded by blanks if ((!$config{"leading-comma-ok"} && /^\s*,/) || (!/^\s*,/ && /\s,/)) { err("comma preceded by blank"); } # check for semicolons preceded by blanks # allow "for" statements to have empty "while" clauses if (/\s;/ && !/^[\t]+;$/ && !/^\s*for \([^;]*; ;[^;]*\)/) { err("semicolon preceded by blank"); } if (!$config{"continuation-at-front"} && /^\s*(&&|\|\|)/) { err("improper boolean continuation"); } elsif ($config{"continuation-at-front"} && /(&&|\|\||\+)$/) { err("improper continuation"); } if (/\S *(&&|\|\|)/ || /(&&|\|\|) *\S/) { err("more than one space around boolean operator"); } # We allow methods which look like obj.delete() but not keywords without # spaces ala: delete(obj) if (/(?W21%fc h@~7>%et'gӺW ږ+Jq#u@jEZ&J}i.5\uH(WQG[NfFǧG mWUfz Ӧieˊeq ^#y⹩U?a-kJ{:pQY'ePN/YMstx FŸڹL򆥬ڜWGxnD^:ƻq\ٳj @Ou ]gբ5i;d*Yɧ(X̚F tNg;]tUݷd|EQZh:^g֑0#`oq Kn>V.SWOUt穱78f'b|W%Eۿg.p_Si)<-M17t u\ Xٓy mna|pӾ$ʊlg#ԋ#=B-GOn:C9W.W-RC2 1DGPª HL̸Qy7O#< f/=DQ  ̈ YRFOs={6Uq)Liӗ)-Wg{d_ ÔC!<4i]+ۚ/y={Һ`Յ3lpYv*T &N~2oT󹥔RZ]fܹ[oXZ?6ZRAutM.Q: FJזSY Pcta'ࢽ|ŷ俘kV>/4xYؘHyu(=бJn4d+7U |]\c7rZdʰ6|W8Rc[ X_q/_W.[|˲.[uj+tL$ŕBOIqDR>dBq"yyE]QcLIcEllHwXBs//Y%MKLzb60E,&U݈T1o?/'-bcK6&g&auM9I?juX=:[9E/4i0YL:ULJ,6׎zϭXFSi).gx;T *ԕyt}9gv~:S_2K$ &z @,9TN)T.nXBub#"n1<9Ul.5 ".񀘜#,dBKĒcu:USSuQGR ǎ' lcšfmz"C)bڑbYW L㎤ou ƉL;&牗%ŒUi뜗l9?ܭ9PK'4թ.0ŸbܡBO,)BXıSbZa|vpw^qAq1!rrrą DYYAL&a6b555B N'>cl ˖-[в˷|l٪nFBb oصX.ޘw˜_wI|ψsS Rq߲廟,\9j1F;mQy]SCay}5Ierݯ`l9pU&p=PQg-ʢ#ltnmv*H>m '"==*zS?z6O% Ъ=Laxl@R/ىVX)Sՠ4XMVL!FLVh+%o`뒔R9FO7?<Ý;T買in^Y- y?cj&"?;#KyHJmLގgcX"n)\L[Uva\?x1Zm$!v;PŇ=z^Щ`2F.} |*#c労ࣧ|gyRKvaŒL͜CÇ7MNEt*icL+h.PQv,TU1)$ %j^ %2T*,.t*Q)gx.fdEAuH ?!  @E0&+X-&9CiPOa+;יۆs\L1|R >mTjw%Yz8kH A I-? qLH?IU-GBy}BO*o@'s"wR] LBtرc"-e}m߁6#v t^նL@_Mbzym&.7)#1iט ľEv+W4B!t{0UrߜwNg8өQ!>ȇDh:Fц QbHzXSN}""ib0 !~yg41OXkYaBBqԞի !aB%LȞ8納d:ogbdUI^y擻DwR2TKkK/a=Mdߵ+wQS[je/nc&!3edgLb=!Lb孈%G*mBTGNei(Sum\ʼn)vS[y}J)tfjYM'>+g^,ebf>1\(VJψ^WܚV(+,8.NO ?UB5bx>/6L姅Ob8fqBXbI쒒^>AܞI<1.Bxڌ_֥:]1Bx@lM_ZŸ_=&sE\m\y빮3~TBl6_ϹNΈ3߇=CO _N3ӯ_?bcc׏ f[#^hTfzO(꼢9"~LNV{+ưܯC58|0RBpxܽ9 @@6RyL;6dܳt*c+!@i_!u8>?VII0COچA dQ +Uh @m.j4wb6@Y|;`U hGgrFN1T*Uݜ1h2c2/5gɰ~AtǷ)[-c:OL>Av4ۂ_́,_րIo[9m*޷…K[@0-ax?d4di%SBІܫϕt@NJlݷ|O08$(-*,->ڏJ: ,;ǁb)*imjuXIV/o\ VC쟼 AjoczTt3%^~$gw~'yj&~)w/\ޒmg KjJ? ͯA,UNt}>K~m. fE}Kx1vYaYW%[ ?5yIPaOguJxTW7l=;%I?C%is:Ұ|nBoodO]Gb ת>' )` 0/0|#w&jJnM t %ބj bhz1+< /mE&(ðO4|Mguoa8GJ)Txy+c/~M31ttͥ-`_t\> 4/9JsgL7r%)ZLF o*ùlnSNc~^6nGA|?n.ƆK7hP+gBgB"H 0FUɜ8Nr-Zx¡$7ف |KJv6 X!l:ZPZ>;k>C|g5)+KLg380r`7;v 9"%[?'xOБ~{9]%Y9pmݝN% 1^05dM&nn T~lX~O` R}|fmn T :23'?O"ۺr=@xhe:]%|2S1gC[ Lz|3wckk-[F7zkmRW6n!=1`{MmMXKuF1TpS?y`~rȮC&y.gGeQxn~-#BԄAx۳:]ylL:Νfֳ5p d]ifW|\HҤRqLz8JJVj?.W|(٩ SOI;}c%0(Yy1 &0o%΋;u KUԓ6ٓ,#NǗ@Ϡּݎ'#B6/x/7T̲2Pf!2 *ٕH) BGQwi Ԯ~wcm_eUTPdb˩6Q~Z?T"\=yw&7i4Txx&{*Pj+Mp@ j^&N,n_/S\rJ:pZkw}[pWFko|o('oxn/6kD+="1ψi-3=yIh*.~(``hTjd-o%XCR&wtgV 0kojO *aBEu*V`s*FSoF|2$sǞ+E3Ǩ7ZbW3GN.UDw?nR2>b"i_x+#^j$>< ,KKs/*w5`)sXk8-/5/țNc^n!ߛ-וp/ PF]keKQVPNW$:}TG 20>̆bs$6 lFޠ5ҙs՟A-i'HsNgXa6K%Jg\߶&bV周S1:<B\O/:s/6<ƥך8n\tϮP'l+BJn3ѣ<.0mSmD"EVh6lݴYV兗WBV+V'N`XTPJzͤT &$՝HVkKUݎ&erTimXy.4/]J(Rɤ#|^)rƜΡ4(ɋa $Ȑ`@E0&+XM㻝&v3ڝ:3dgat3WJOVxUr}}ɾmx*@07/3 UULJ?IBBIo9UǙV+$x]0Xp8*5<7s=c]u% ٩iEji#1~k3 E}'ӶNۨ5uG$׼{D"H.f$|x~<04Gϓbia ^)6Q/y|0X DS*1ʎ`ˤLTYՁXMt+zN Ӯ\x5;wex5w{WϓU<u?EdU`-RY} ᜻ QOi41T3ìbu>VA[-YZ IDATYw"ã)Q!4 ֋Qpg-z1#Iǔ[p;k3G:;Y`HǺ(__WGde`M( J83q|+H$7$YI>OFeIsu5{_VȰKVVaQ&#(jd#`pjMe \j-¼s(+fљbŸ֗`MtB+  }rPLf[퉓ݷ|,u"⒓=pϋ/:6.&amU[CðޘMcybL~Rg3cN\S]EQH$ɍ?}>wW=gݫϏAX9~p7A<33VVNGc/zychy#~Ĺ)cT9]$R7QŔj: #ܣ TR`hwk8_A6CC;h+Jp4Od#6ZƥAU&+\c1SN['l:zF nD"X=0l4*Eq<V jf`aNW]N'ϓmг'3YVOGrsH,+#d[{_YIvU@AJyyq3 <SVJL^mĎnV6tBᅃ%hJUƳGOùV) qł;;>+Pޜg ӲqQU.fˎR}mTVAj[N$Αl?msV*rjj) m#l[ճs=vM"H$6qFȉLy]o Z ] %obF_!\$K)6|gK ȱ2" ZYh=&07=L!Ly&H%[gXgzJʺIcɌ ?gDZ||û0j$t QeY_,ag"Tٻ\XidП_bH{דmGmʩؖ:c_]kIB#sdH naRTotFx4[Ji<D"\U㌟BF=:_ %Oa|>Std#,3WlMHaΞirF*sn VO~`FBʍ3X3zgp*v?Nq!+gkM7=(qS]4R%[O||wMLxںG62 UO._[|>Pwd0?H?Eua6cLfcSD"H$7A]y{QD)i<Cx7w"4B?O0bMTݹ-]98=#98syFb:8⻛mjf=LjNJs ٓ\NmΑt‚0u#)sF]4Qݖ&{Z ;^_ng~OtJ$D"qzg2OF3!7=wĜU Ne{z8a!W:;XTq{_"p.]#гkۺ뵀lVY z6/xuRE[l0&t gm0 j?1_yO PnyPۜfsH$D $8 ?_4|PS2@Tg)`@_hxN@WUX9P;-f3_ґl` mm4_S[jʫ-eJ(_F<|7,^4DtC~ȢrJ 1a:=JCgmlFpϣٹ: 4hۏb#`F:c#/׷:4>D"Hl/_H DD]kî,z#Zf=b4bFV۴FmQh4lc4hJHw9\=^ wɆ4|jϽm7 ,صa#{.GL{amzLdҐ(d'ƿobe?{LWR}MzMfΨK)6|gK ȱ2"u9#'2Ɠ/Y< Cm1|FY?dGsF}YU9!ׅ3X3zgp*v?Ne1;p߄n(VrMuUT7tt}$_E@ECB0L}%nL7xzV]G2&'Tcq;2ܛvq2"1qD"(rWVtFߚf@ 0&wf1nۛ`4j:a,n2׹YTq{7?lv]@p:W?gq剟ѩ%2g:]IiLS[1\ f$<1Q1;Mc5P&T`<30ڷXγӓ[14TPz5tHN9FǼDȌmQﵟ `'5._SGm$L~/.ʂ[_F2}\m7`oM$%#y]Ǽ$lx0h-,xkm_ȾlOɿ8~U{Y2}cgFR6oّ5D?5'7/ff[]cɝu '|o^a1AcROHnfW(f2~ FD\H$0jCբjFZFRRX,ff3֭XcB"_Q 3~MPk]M&Mޜj[>]v2Y ߔI<?s': ܶkZN)/.M#n@ RNwn8rvx3r-9ӥeL?GN$ !'~+6ua155EIwoWAiч1 D"9\5ts W鐸Kfh\1>uH$6^D"H$9H$D"AzJ's7^$+-ߛtӍ%dΦ oBѭ}赶W]?qFgdNwoo JsOSbrsX盝f o;HWm"_W҂ vц*&>?DNBO(;c7m@oa}3SOtGk]%3`.D|љWWRxkG? =,zP}?cTz:3vm u_ Mr7l?W\xfc),>Nnyk:fh=Vlnu卣 y$;|.Ҟx/j2}nنD& q0g,t{gi}}bk18{r+ofo$֑?1qzAL1jFOc=raO^9KV/0U7%^dl\ׅ3m|.>]|=C픇}ngUl;Y@HE*S*Og%vڳJyud"#xs^1x6ڗHn0䝥 ֱe)Ű ͗5Kٙc|.}0V]`ZP7wXNC O[vs|+7I#:k&-hh@ј?gDZ||û0j$t #+i+_B~e Q(XRwj&}1sꃁ]Wa+MߙCxh=Wm9iԞk)=ņֳl ]9QFĴV/ck+8n2tF$걮dm v;$cc)w]r$%I̜@ڹ΂l 's?2:2|}Sg0sq]}'50e IDATcEa\9ᰍmkWmvX"߮Z y7E1ngm<kig#KjҷdJ d'(KNvZB%gޜI?04_Y˾'APDwKd ^9wH~ >@t|g£Jk-%CdxG$wobӶ|>B{}k7Uwd0?H?ElG? †0\hߏx{GRpgjZ|}>Jk#~lw X{wqQVi3 (/H`Joh)miomմR[7-݇\-Qvg34,E17PDDByf3 =jG>u:/\sι:q<*.SjP79;,70$wݷ^cKճ7^'QrzZ°Z8ϱx"Ϸ< 6<&!⿠2l0;WpKu$e{f ӱf=uVxxٔKG|6/A{u6x /DqO7Z[֜vd!Kӗ7.!YB>m8)o,ك9+cr+/#F Zx "u%O:Z5=(l`#?Ŭ'%3Y85WŅSEv#Uf#g0}X0(œ`%^oH%!v*CUيo,dBz/ċx3Z 6r10၌1Kυf߆P04 %v IlRIQE&ԇ %'8N'7Б>, |=˺yЩb%xѱsw={;evޮ%uh,?ߝi20Y6#o@;+oK?3}zoM*(@AC3ydQ %xLokuEU7Rc?iw6c[f9.QO^RQW=nLkuśV.{@~0c\5Lc&3w_3.#dm @Xhbe Low7ILf@]T@\|:E7r'/~{7xA=fZ [(W)3g7,.d"y.uQvdZ?Yʺmj~/_rȵ }u㶳`.xd56d|i2鉧 Z34C -F"ۖymK(ō֮75J?&:mv&C-i/%lt65LXG4Lv]ɥAmIvR}9y3i YIM=O떍n.5~V WvRu q+)m|Vo*.&/+B]҈_BR6,!T/Gc}_ΦkBoSq ?-XeSE4.ޑ~&]ʎ '%i/|2>dmLJ >de5\䰑VlwGw$vesrH5{s9HcEB2%ۼd t9dY7z(%-9%h:P;O旙Rrv.Iٰz(,̦ VKa^%uTK3`д"| 6ׇZl4 ½̜=دsnٱH|vJJoT@mn48.7J6J^~r>X6A&Οe+ؙg i 19l/*74 zmF l㢔އ R ˗'?#득]laBFco~3 Jt&4}91ޝ665,[~زl0`&`irC>?{>j40Di\ft:wwe|DYۦ o&/\=XF8ef&41g B'-'ߖk`ڼB/e̓oŇ>CҦN34l\" qK՜RMS/O(*tSPfh4Rq˻M+~ ]rǽ (FxӦ1O8fkMqǝ13_7GG0<*}@ S^@  @ '41ֵ|g^˽XD 0|g|74iާ̣i~ _D:Nkߦ!m 6:7*r2(woF~1 )$2L{j_F4u26>{ zaFtMmA~T=Ȣ.L|wAJ9^]X10TWChe|7];eR#b/ӹ |vϖfO _}py}8)Չ(z7&Z,)h4h aqd-b힡|Yne}M3ₗ PXΡ.䣜k2+`f_Y[1 rΑLu;L0WEpwmɋ;0bbS4p:Zꇄ=ˏu>LZ$+ʙ|r'%KsF1$ΛsWȶO~$rPi۝͝`hշ w֩cK9_s mEU'n}6#g|ם\TR Mx+ؗ% fCJ-A -vO^|{˖/_l˖/?l|.eҒG))%?T{w%*,̕ }qƯ$@WTOyiO?I# Z|)r',-yۥjy5ǿZ?I U'7~HD ?6JcßV-rғ #V(٘VJ%N uʷ K[HisH Z)]nRHOڑӘHϥVȣ䬤I9 iRHKKdVH\wVM.4)ĊZeҴ:g8SG 1J'?&!EDDHc&%joGR~Gaߥ祄,Ǥ%Hғc"m<Ո4)vߥ#=@x^ZxZ23 [,o섷i,y.K_Ζb_Nή-:Ǝ+E=N0a46zX?jQj$[4hrH&VmRiªϤر,rߞ!gԟT-m\2C~I)::L%Gi?9vZWLפأ'o+eϦkR )J{4U~'i~ni=riSs+%I2K'H$u?xHzi)D/II641{c'qGI5u:ҏKM9ρv}0I'%eҷgOHSI.J֢0V_5iRgI~ J'zo4mi}rdMci9?V,} IGw}&M6Gښ~M'w^{Mӥ_(YIOKH/&W3KӦM`{&}6ӏߖM#}+ۮ>,-_Qte˖Y|֥˖m/wtv]Wy] Y{T@QAkF VnOc ?ҟ߲uǏd_snqBd0u7n u?}˥7F._ڴ=,;n-xD1㧲!VI^`:kTgHܜS[: Mk8}h/Wԛϼ?[6R5 )[e GIvWM7 mzœqmHU Ցmtm߃{Q_*NtPļf'y1f)^SZ?cyR>.T'cH3\<\bjT~h-<ǘz>Ekx"/6* a|hϵLtv#c+"1lD@pd8! AφSj>0 ~pfG2i+wCk^#z[^bV9"0zj6MɋMNYti}H^IoRQm^k) &)^rhvnp柉a^Ѓz\DK/f`!烙Jf}UH睋`.cgyJQجV9>3Ym̦SLP1)(y|yS1i.T&!⿠2l0;Ws5gq:[dseY'YMt'Ha - 0si6.7y`8ًH>ߒgGG'شoȳ  }ҿ>K%ג $\ oû1dț$<?d8H''.VhDCthZ&Kg9O>={$:o?9n=(Y ?@3]ZUN@urrGq,`ږCu G/i#~]\ƈL=U _\Ҹ@3hX>m'r3]_rg"vTudO^^ /& GrVG_n+z^?ȟR?SEYMu{ӿuu|M&/Ohs~%lGx,]ٷ%< {jo11-G̖y&Oym7|QajOY|#b`bԋtDłI3*N1:b`՘)ۏdҚWn䨐{Ezt{KNM L|5myq ``ϺVLg_M?A2Y \6OYj/7ч#"--yUj=]ju*tʋXLh`ݕ*|0Qo!4gGcj1hScV kIwc$HO\JK~%ѓ?q AwxϺ!V0.d"y K]jC-v֖o_U-,Džwޒ %JX=>#qt[mFiG'G/mf'r! W&Y}x%}[߫bk5x٢5I#$Zq{괠£U+|B9Ty 4]\9.ߴ}rR_P< w"3O %o- ˗]Ɉ:@U11z.7=4PpK5ݥgO +kv١`;T%};_ZeX/DnJ>>LṋԃClɕ\ j $6ON3m&k쥩F#l XvDmWm;-/a>}ώ&QχFZpA*zud؊8%Nj}Z &ޜmfepԉNTSdg;z^]0ei L|¹<1\޹y'?(@Qvu\l(3Kq{?yf}7?ݻws4 GV?̂\v|*0i@9q~^99teݝN9< .0샵d^/>'Ty?) "^܃ ./n{ ?_᫝p$JYPNvf6dak#uѐNٙ։W9Qd[P@vNf{xz`*?&B!o>Keߩ=dYRI9^z̲ *==ZgdMW:EQU#+&K릛lב}'V;K.}<Ư(gFW9mɏG>=»Cw+ШәŚcIi&@enzoL8GO5zYqssd3>d O:+';SM`|_6#?`t:Ի \%5=*>(]O*7y3# {/k=:pc`^SB1cycJKt MFEYbJgb;on7&ڢ}Л''-cW,ݏO$Gk5 TVjGx6<2Xr)j[LMjoV9+`뾺˺LbwΟՙi^~!SV}e+^oes|u^l@`eVXҝ(w ;\3})))W k[06'0w7dzn2UfNXLYP$SZeC:^:[H $4x=[}/&]|$$^}rˉ62BiYL+Qyx܁7J% 7͘_?0vTME7M;m4vCیho[tf_SNz޵{PapS+#GpoF6k[d~M>d(b-5Quyz@,?z* LPwM¹1oL9c׃#앵\[21<\#rmY}lĄ^d.<ީ'3ڨ5Yf21)hm_6u6YBPjtl7g'| 7FدUR~ŐƜi+(gi1A /q΀ݝs4c0,;Ȑ+7aWWWj5* RiYP`61LL&6l؀Q$P(($Yϐͥ,PC?ET;Ozߴr [~iyآrCL[:U*-*=<8͕~GS?P~5\/2g3]۲/ `&?s|9fCjKN00䧟b#'ڙ\?rǚG+kt'ڙ#rmY}Ei# &rNYpAk'L9'|=9M jф3er_fcyss[؝D7앿`ڐ&f`#zW8JJ@n+^OfITN3+~15Łw]K[<(U`ʌ &z>Ğf7{F/ݽO @p"?@ :3ҥin=@ ;}@^@ ̯S 9I,R=k.QjlO)$l@oW)K6ո_ V@ {&~l3]5~ W}X>UlϋdԘ1UWn=JOڙPDG?$ b@saV|5Je|N|(@ ϐ;KDx!*l7h!zRV/?:vNgrEYfM*WSIH Cr ϬӴ7{Iϭ"<ٓB@ q:3- IbU~ezpv _OT?3n!7͈Bq&Gt!oHj|@ p~d9tԬE j]Cvrg[9S޼KZ!Arޓ>&CpA½"Ri: C@ Xq:s)|e7_OkRZwCi׉ڑ|Έ p{ _4AѠ4ŏLOzP2`܀ 垢"4@ Asq~ϽS\KJV%X @ %~,ql?Sk[rg1_`y|)5@ ~/wEB |u &/py@ [.n](G@Ovq'3z-h l[' 5zE'.mEJfz7GmXjKbaN:=},L@ 9wToΎ,Hn 8).ts%!CܹWГ}$א^3PRYOsHʰ\IZ.Hڞ1Iӳi4y\yvkΰ>.K~Ibٞقc0AlGӗM_u$)6bO m3deE{y`Ȑ4a`'e wjAX":ח;\+gQ}Kw%yRd1_{ y[Y$z VAd+.}G =ԓb%xѱsw={J7wn|c?-)2M#@ MTwlB}F PcoIx[Ix_F^@7ITWmg] "D\نmTJ-]JF@ wog: }L&Eu[H|)6M1ޝƱ qu+iO/35kZPe @ g[9S޼KZ!ahKiYƚP&[Mqj<1 (|5g@ d{wk#IIK WvRu q+)u";,Ώ11o7T*AٲUf0UQIz4~xz 3sLbiRxj \⊮Ib@ 6GZ0bLtI\1:ځXԪ~G1A6%O2EpkW/;ty!7`ʔg(~ bY%0Oe_'GMz}*7<ݛVZ&I @ F|$$^}rmX}&Th4NMZet8-] `Ʋ+z8_,kߺ)Z6l*t`@ij=]L @p+7774 WWWj5* RB@P`61LL&6l؀Q$P(($71L˸7Iu@' 5?{'tnGc'xsdcS>Š H@ f~ <;%}'s(.-ULӏ޿Q$'Z!@~ a~m3 a!@ ޷z@  @ 'ܓ fsΒS\ -UddQbs@4u;@  l">~ %d"t%DZLUҚs{79sXz#i[) m-*@zzqS7/˴riMy[l@)h!31qd?@ pUPڻp/.<9z4;Yl1䓴e+_" VmֱdZFuN];yI,Iq3xC? 9I,R=k.QjlO)$l8W {rϽK\C';Q0`k#bфx WN,IzJBqq@@  [@ 3"@ O'?syЙ%ΝUddQbp$,YYYdeeQ:'@p{2+;5䖜ugc*#..5%2slH;tڿ`.b|8g Wo$x:@ Uܓֻ-EHOo3nj;hxj#-1V590[xN4l~oOΣ}d$VđE@ *zF|le5<;א϶u}oQ2w]6liIYOE((pPa?@|~rvXf|OcIbgYs TC&lf{J!ab?@ ?N/re 4D[Ü)WN,IzRl3j33%Qn4wIϞ xѽkW#{ҶA~rvgwg#H 1f_Pq)y@ w6#~vob9|C,U=  XȾ/3Ӈ%3YtR9!WHvto2":;uJ Tr*u# !t([aQPV Gv0H7]? Z7@ ~ܓk Y v3g4nl*3(ǤqQXMp ¼d}z #cϞ=;pEtk#cgkǃ9`~DI-I{r2=Բof|Z7@ 8K^^JJZFRRP*(J\\\pqqAPP($jjjHKKC,T@% *JߓxwAIΔ`Th`y93 JR霜#;0 (5JaP7"6SB /@ qRRRpuuEFAZ*ʺl6c20L|k$IB@kq`M&gvI{r씣AMW+q{~I"7} FX9C@ D'pR@ ~kWo @ ψO > dd\@g6SsۜC9YnZ'<#dIDAT @ '3_Cn9>Y;. tgc*'n6slHʑ?)Eݰaꍤo]@ {2z<mM}ahjgn6rva M`Cnl~oOΣ}d$VđE@ *zj#9&)Wk~MSK²:;C:ZeIڲ}/FW\3Y{7u.ۅ'Gf`'-)kIh|E* <C>֭c!DNϊ!/q;$v5@5`Rf&s+WINԼ5y!ؒ`l9Η^~tjr"! rg7}Ď<#p IJ)Ȩ #bфx wIϞ xѽkW{eq{;yBL1P2raC(K2``Z 5P[񲸄LyRfA.eb@RYd&(7g"1BnϾHt&>yͿsdO.+W2cuͅ_GQg'_u9m2}dAG4^+/"""}YL]ḮX8X}R I,?bKW0%+LVG&A.b/zY432]0e^gfeZ´V}+ʭAVρV"""V׉cS)ma%/L HI ߴ{2K.zG5T|@r^w'!E'D~ǯKNU'z 344\bz*1̞x($Se\Z$'&tϔnqlGNyT:9;cxu,/¬'gj'sq:J.Vf>sd[/)Ҳ2 WW@pxt$,yv`g{t-`lzÕ8$w?;%frX]}mFAx;w¬_9ҁ@:6#zjшFVf+QL֢=:I7p:i߬p^Wv7^O~?N16q>- %/"""w\EEXEbb"eaYrp:c0  6mD0d۶m{ x0m[+~ mEgI_(keRhug7:pb7|{}#s~B%}"""Q+&NYC3H1u<9Yz>CUλȟW蓜"""/aJDDDDbDL 9vsAOrz?ADDD~՟FQF~֬_HNJN~㐐͜9ϟJwwǰ⁔~Ϯ㳄14Ζob['("dw}쪞yHlPݐڜ?j_=O +;{qQvx-^mkΝ;wo<_&~]Wcw9˞fF kCN^X6z#qe ˲?3Rxr/L_?6>Nƨ1L\2g-faYRǏ \hVJJ +Y@鱭D WnS_׶tbJ}kHI7$2%3VJn 4uYvrF?fܵźaH r] yO]Y$d0g70& {V9DI dZ v1h|XGMyI_)e% hw`wEIgYxOlo)- k_`ld460 %)|.% Pay:MDD$V˧zOem BsX___7;+{tr<^> >CuTU~iO-Vh&|:{#"?OU?eTҀ8PzG0̜~O#H?$,yv`g,Y[@٫*s/Xy +۾˙ @}^_}qa'tgdL^.:Drbj ~lHF،xE7>~eݮ[AXnwX^ .UmVg/f?^)"""߮ ,D,²,㉋rt:1`! lٲV۶mm{bků SOa.ulu _Ƕ7 &S{DDDDb?ODDD$F(}:V˖eD:ק?_#RTؑGDDD$oԟ↕LĦpDDDDZN $5.NDAH """բ @ԿW̖wQt@""""Q) ?t圬F:)Ư`DDDDVT3|t""""Q-:?H """ݢ kg5qD>So5OwG:֧?Ⱥ'<2~SMj}:KOt""""CAH ;mVa̜#Fv5p&5!ұi\.5Rdkكn{/eЁɌ3aE!:xw)*Z8m9ᡠ t`""""rkB!15Sr8Fql;ހqEDDDm0\ \rv+2.c.۶mCǕH*""""ءK`jm_˜za7q1Ncƶ}TDDDDnmm1.c\m_ĄB!ۘ+m_qht""""rkBv1FcFh%gEIENDB`node-bunyan-2.0.5/tools/statsd-notes.txt000066400000000000000000000006021377616564400203070ustar00rootroot00000000000000 # building pycairo (needed for graphite) wget http://cairographics.org/releases/pycairo-1.10.0.tar.bz2 hack pycairo wscript: #ctx.check_python_version((3,1,0)) brew install cairo LDFLAGS -L/usr/local/Cellar/cairo/1.10.2/lib CPPFLAGS -I/usr/local/Cellar/cairo/1.10.2/include PKG_CONFIG_PATH=/usr/local/Cellar/cairo/1.10.2/lib/pkgconfig ./waf configure FAIL so far. node-bunyan-2.0.5/tools/timechild.js000077500000000000000000000041151377616564400174240ustar00rootroot00000000000000#!/usr/bin/env node /* * Time `log.child(...)`. * * Getting 0.011ms on my Mac. For about 1000 req/s that means that the * `log.child` would be about 1% of the time handling that request. * Could do better. I.e. consider a hackish fast path. * * ... * * Added: `log.fastchild({...}, true)`. Use the `true` to assert that * the given options are just new fields (and no serializers). * Result: Another order of magnitude. */ var ben = require('ben'); // npm install ben var Logger = require('../lib/bunyan'); var log = new Logger({ name: 'svc', streams: [ { path: __dirname + '/timechild.log' }, { stream: process.stdout } ], serializers: { err: Logger.stdSerializers.err } }); console.log('Time `log.child`:'); var ms = ben(1e5, function () { var child = log.child(); }); console.log(' - adding no fields: %dms per iteration', ms); var ms = ben(1e5, function () { var child = log.child({a:1}); }); console.log(' - adding one field: %dms per iteration', ms); var ms = ben(1e5, function () { var child = log.child({a:1, b:2}); }); console.log(' - adding two fields: %dms per iteration', ms); function fooSerializer(obj) { return {bar: obj.bar}; } var ms = ben(1e5, function () { var child = log.child({ a: 1, serializers: {foo: fooSerializer} }); }); console.log(' - adding serializer and one field: %dms per iteration', ms); var ms = ben(1e5, function () { var child = log.child({ a: 1, streams: [ {stream: process.stderr} ] }); }); console.log(' - adding a (stderr) stream and one field: %dms per iteration', ms); var ms = ben(1e6, function () { var child = log.child({}, true); }); console.log(' - [fast] adding no fields: %dms per iteration', ms); var ms = ben(1e6, function () { var child = log.child({a:1}, true); }); console.log(' - [fast] adding one field: %dms per iteration', ms); var ms = ben(1e6, function () { var child = log.child({a:1, b:2}, true); }); console.log(' - [fast] adding two fields: %dms per iteration', ms); node-bunyan-2.0.5/tools/timeguard.js000077500000000000000000000104641377616564400174470ustar00rootroot00000000000000#!/usr/bin/env node /* * Time logging with/without a try/catch-guard on the JSON.stringify * and other code options around that section (see #427). */ console.log('Time JSON.stringify and alternatives in Logger._emit:'); var ben = require('ben'); // npm install ben var bunyan = require('../lib/bunyan'); function Collector() {} Collector.prototype.write = function (s) {}; var log = bunyan.createLogger({ name: 'timeguard', stream: new Collector() }); var ms, fields; ms = ben(1e5, function () { log.info('hi'); }); console.log(' - log.info with no fields: %dms per iteration', ms); fields = {foo: 'bar'}; ms = ben(1e5, function () { log.info(fields, 'hi'); }); console.log(' - log.info with small fields: %dms per iteration', ms); fields = { versions: process.versions, moduleLoadList: process.moduleLoadList }; ms = ben(1e5, function () { log.info(fields, 'hi'); }); console.log(' - log.info with medium fields: %dms per iteration', ms); // JSSTYLED fields = {"name":"cloudapi","hostname":"5bac70c2-fad9-426d-99f1-2854efdad922","pid":53688,"component":"audit","audit":true,"level":30,"remoteAddress":"172.25.1.28","remotePort":49596,"req_id":"574e5560-6a9d-11e6-af76-3dadd30aa0da","req":{"method":"POST","url":"/my/machines","headers":{"host":"cloudapi.nightly-1.joyent.us","user-agent":"curl/7.42.0","accept":"application/json","content-type":"application/json","x-api-version":"~7","authorization":"Signature keyId=\"/admin/keys/64:86:e3:ef:0a:76:bc:43:02:8c:02:04\",algorithm=\"rsa-sha256\" PJmFgjoiW/+MqhYyzjtckFptmcFrHqV1zuETRh+hv8ApxyKZ/+xO6G8q4PPNDxfbhAsP6/kKrV7DJklyIn0KunkyHbonAUGuUb4eq0CghmVX0jwma2ttdvNB2n8k3rvUDlQXp+X/Bi2PNj7D1zjcBQlkRhx118JTtR+QZp+bdTrJ+g6lIs1CMPnRHEQkGOYw3mjDjRNwPiPqcQPmGj7qY/DW0lEfIj/41z7dWS6vUA50RrV1EeM1hD7VCKYZAC41hFC/VLSG1Lbhq7gTykZ3QjM0WyOaDX06cKWxdS+x4VveyvFMVUaiGCeiWpOXmbiLbGomII2AR8NK1+LWfaqH4C31y0bjZ+iK7SBMQ+XY3QjlFv/di3CdlEylUKXsJoKxGqhuCzg+7eXzCNqMj5tdvOdKwizPpazwzPbjAyDeU2l8dTwggMQSuLy7qC7UVtRN2AUgWxw8fxGqivnmbGfRE+KFxA+VrizG+DLFQBve/bd3ZQvKmS/HKM1ATomYyW9g7W8Z2lKbmPtbv91A77bLRh7f6OA2fwaBPW3HP89adC2Gsyj+0sCcPq3F+r/lAT3gEw+tuVBlBbJsS1IV19FQAl0ajCd9ZJ/mJMPGt5hLwbVA7mU6yyU5J71elaBs6klmaKBNPesGLBSv55/xnZlU6mS9FXPdC5Sg=","date":"Thu, 25 Aug 2016 08:24:28 GMT","content-length":"184","x-forwarded-for":"::ffff:172.25.1.28"},"httpVersion":"1.1","trailers":{},"timers":{"parseAccept":743,"parseAuthorization":2051,"parseDate":20,"parseQueryString":50,"bunyan":79,"readBody":1699,"parseBody":218,"restifyResponseHeaders":9,"xForwardedFor":108,"setupSDCProxies":18,"accountMgmt":114,"signatureAuth":182865,"tokenAuth":42,"assertAuthenticated":15,"loadAccount":56,"resourceName":55,"loadDatasets":10765,"loadPackages":9280}},"res":{"statusCode":404,"headers":{"content-type":"application/json","content-length":99,"access-control-allow-origin":"*","access-control-allow-headers":"Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, Api-Version, Response-Time","access-control-allow-methods":"POST, GET, HEAD","access-control-expose-headers":"Api-Version, Request-Id, Response-Time","connection":"Keep-Alive","content-md5":"O3boRcASC7JNu/huA6qnPw==","date":"Thu, 25 Aug 2016 08:24:29 GMT","server":"Joyent Triton 8.0.2","api-version":"8.0.0","request-id":"574e5560-6a9d-11e6-af76-3dadd30aa0da","response-time":210},"trailer":false},"err":{"message":"Package 92e2b20a-0c37-11e3-9605-63a778146273 does not exist","name":"ResourceNotFoundError","stack":"ResourceNotFoundError: Package 92e2b20a-0c37-11e3-9605-63a778146273 does not exist\n at parseResponse (/opt/smartdc/cloudapi/node_modules/sdc-clients/node_modules/restify/lib/clients/json_client.js:67:23)\n at IncomingMessage.done (/opt/smartdc/cloudapi/node_modules/sdc-clients/node_modules/restify/lib/clients/string_client.js:151:17)\n at IncomingMessage.g (events.js:180:16)\n at IncomingMessage.emit (events.js:117:20)\n at _stream_readable.js:944:16\n at process._tickDomainCallback (node.js:502:13)"},"latency":210,"route":"createmachine","_audit":true,"msg":"handled: 404","time":"2016-08-25T08:24:29.063Z","v":0}; ms = ben(1e5, function () { log.info(fields, 'hi'); }); console.log(' - log.info with large fields: %dms per iteration', ms); console.log('\nNow you need to manually change `Logger.prototype._emit` in' + '"../lib/bunyan.js"\nto an alternative impl. Then re-run this a ' + 'few times to compare speed.'); node-bunyan-2.0.5/tools/timenop.js000077500000000000000000000011071377616564400171330ustar00rootroot00000000000000#!/usr/bin/env node /* * Time logging below the current level, which should do very little work. */ console.log('Time log.trace() when log level is "info":'); var ben = require('ben'); // npm install ben var bunyan = require('../lib/bunyan'); function Collector() {} Collector.prototype.write = function (s) {}; var log = bunyan.createLogger({ name: 'timeguard', level: 'info', stream: new Collector() }); var i = 0; var ms, fields; ms = ben(1e7, function () { log.trace({ count: i++ }, 'hello'); }); console.log(' - log.trace: %dms per iteration', ms); node-bunyan-2.0.5/tools/timesrc.js000077500000000000000000000014651377616564400171350ustar00rootroot00000000000000#!/usr/bin/env node /* * Time 'src' fields (getting log call source info). This is expensive. */ console.log('Time adding "src" field with call source info:'); var ben = require('ben'); // npm install ben var Logger = require('../lib/bunyan'); var records = []; function Collector() { } Collector.prototype.write = function (s) { //records.push(s); } var collector = new Collector(); var logwith = new Logger({ name: 'with-src', src: true, stream: collector }); var ms = ben(1e5, function () { logwith.info('hi'); }); console.log(' - log.info with src: %dms per iteration', ms); var logwithout = new Logger({ name: 'without-src', stream: collector }); var ms = ben(1e5, function () { logwithout.info('hi'); }); console.log(' - log.info without src: %dms per iteration', ms);