├── .github └── workflows │ ├── check.yml │ └── test.yml ├── .gitignore ├── .npmignore ├── AUTHORS ├── CHANGES.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── Makefile ├── README.md ├── TODO.md ├── bin └── bunyan ├── docs ├── bunyan.1 ├── bunyan.1.html ├── bunyan.1.ronn ├── img │ └── bunyan.browserify.png └── index.html ├── examples ├── err.js ├── handle-fs-error.js ├── hi.js ├── level.js ├── log-undefined-values.js ├── long-running.js ├── multi.js ├── mute-by-envvars-stream.js ├── raw-stream.js ├── ringbuffer.js ├── rot-specific-levels.js ├── server.js ├── specific-level-streams.js ├── src.js └── unstringifyable.js ├── lib └── bunyan.js ├── package-lock.json ├── package.json ├── test ├── add-stream.test.js ├── buffer.test.js ├── child-behaviour.test.js ├── cli-client-req.test.js ├── cli-res.test.js ├── cli.test.js ├── corpus │ ├── all.log │ ├── bogus.log │ ├── client-req-with-address.log │ ├── clientreqres.log │ ├── content-length-0-res.log │ ├── extrafield.log │ ├── log1.log │ ├── log1.log.gz │ ├── log2.log │ ├── non-object-res.log │ ├── old-crashers │ │ ├── 139.log │ │ ├── 144.log │ │ ├── 233.log │ │ ├── 242.log │ │ ├── 244.log │ │ └── README.md │ ├── res-header.log │ ├── res-without-header.log │ ├── simple.log │ └── withreq.log ├── ctor.test.js ├── cycles.test.js ├── dtrace │ └── dtrace.test.js ├── error-event.test.js ├── level.test.js ├── log-some-loop.js ├── log-some.js ├── log.test.js ├── other-api.test.js ├── process-exit.js ├── process-exit.test.js ├── raw-stream.test.js ├── ringbuffer.test.js ├── safe-json-stringify-1.js ├── safe-json-stringify-2.js ├── safe-json-stringify-3.js ├── safe-json-stringify-4.js ├── safe-json-stringify.test.js ├── serializers.test.js ├── src.test.js └── stream-levels.test.js └── tools ├── colors.log ├── cutarelease.py ├── jsstyle ├── screenshot1.png ├── statsd-notes.txt ├── timechild.js ├── timeguard.js ├── timenop.js └── timesrc.js /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: Check Lint & Style 2 | on: [push, pull_request] 3 | jobs: 4 | check: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v2 8 | - uses: actions/setup-node@v1 9 | with: 10 | node-version: '12.x' 11 | - run: npm ci 12 | - run: npm run check 13 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - 'docs/**' 7 | - '*.md' 8 | pull_request: 9 | paths-ignore: 10 | - 'docs/**' 11 | - '*.md' 12 | 13 | jobs: 14 | # Test once on every (available) plat, using LTS node version 15 | # (https://nodejs.org/en/about/releases/). 16 | test-plats: 17 | strategy: 18 | matrix: 19 | os: [ubuntu-latest, windows-latest, macos-latest] 20 | runs-on: ${{ matrix.os }} 21 | steps: 22 | - uses: actions/checkout@v2 23 | - uses: actions/setup-node@v1 24 | with: 25 | node-version: '12.x' 26 | - run: npm ci 27 | - run: npm test 28 | 29 | # Test once for every supported node version (don't repeat the LTS 30 | # node version from the previous step). Only test on one 31 | # platform to not overkill the number of builds. 32 | test-vers: 33 | strategy: 34 | matrix: 35 | node: ['8.x', '10.x', '14.x'] 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@v2 39 | - uses: actions/setup-node@v1 40 | with: 41 | node-version: ${{ matrix.node }} 42 | - run: npm ci 43 | - run: npm test 44 | 45 | # Test older versions separately because really old node/npm don't support 46 | # 'npm ci'. 47 | test-old-vers: 48 | strategy: 49 | matrix: 50 | node: ['0.10.x', '4.x', '6.x'] 51 | runs-on: ubuntu-latest 52 | steps: 53 | - uses: actions/checkout@v2 54 | - uses: actions/setup-node@v1 55 | with: 56 | node-version: ${{ matrix.node }} 57 | - run: npm install 58 | - run: npm test 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /tmp 2 | /node_modules 3 | *.log 4 | !/test/corpus/*.log 5 | /*.tgz 6 | /test/log.test.rot.log.* 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /tmp 2 | /node_modules 3 | *.log 4 | /examples 5 | /test 6 | /*.tgz 7 | /tools 8 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Trent Mick (http://trentm.com) 2 | Mark Cavage (https://github.com/mcavage) 3 | Dave Pacheco (https://github.com/davepacheco) 4 | Michael Hart (https://github.com/mhart) 5 | Isaac Schlueter (https://github.com/isaacs) 6 | Rob Gulewich (https://github.com/rgulewich) 7 | Bryan Cantrill (https://github.com/bcantrill) 8 | Michael Hart (https://github.com/mhart) 9 | Simon Wade (https://github.com/aexmachina) 10 | https://github.com/glenn-murray-bse 11 | Chakrit Wichian (https://github.com/chakrit) 12 | Patrick Mooney (https://github.com/pfmooney) 13 | Johan Nordberg (https://github.com/jnordberg) 14 | https://github.com/timborodin 15 | Ryan Graham (https://github.com/rmg) 16 | Alex Kocharin (https://github.com/rlidwka) 17 | Andrei Neculau (https://github.com/andreineculau) 18 | Mihai Tomescu (https://github.com/matomesc) 19 | Daniel Juhl (https://github.com/danieljuhl) 20 | Chris Barber (https://github.com/cb1kenobi) 21 | Manuel Schneider (https://github.com/manuelschneider) 22 | Martin Gausby (https://github.com/gausby) 23 | Stéphan Kochen (https://github.com/stephank) 24 | Shakeel Mohamed (https://github.com/shakeelmohamed) 25 | Denis Izmaylov (https://github.com/DenisIzmaylov) 26 | Guillermo Grau Panea (https://github.com/guigrpa) 27 | Mark LeMerise (https://github.com/MarkLeMerise) 28 | https://github.com/sometimesalready 29 | Charly Koza (https://github.com/Cactusbone) 30 | Thomas Heymann (https://github.com/cyberthom) 31 | David M. Lee (https://github.com/leedm777) 32 | Marc Udoff (https://github.com/mlucool) 33 | Mark Stosberg (https://github.com/markstos) 34 | Alexander Ray (https://github.com/aray12) 35 | Adam Lynch (https://github.com/adam-lynch) 36 | Michael Nisi (https://github.com/michaelnisi) 37 | Martijn Schrage (https://github.com/Oblosys) 38 | Paul Milham (https://github.com/domrein) 39 | Frankie O'Rourke (https://github.com/psfrankie) 40 | Cody Mello (https://github.com/melloc) 41 | Todd Whiteman (https://github.com/twhiteman) 42 | Zach Bjornson (https://github.com/zbjornson) 43 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to node-bunyan 2 | 3 | Thanks for using node-bunyan and for considering contributing to it! Or perhaps 4 | you are just here to get a sniff for what is going on with node-bunyan 5 | development. 6 | 7 | 8 | ## How you can help 9 | 10 | If you want to help me here, great! Thank you! Some ideas: 11 | 12 | - Do you have experience with and/or recommendations for a good automated 13 | testing service? Ideally I'd like support for Mac, Linux, SmartOS, and maybe 14 | Windows. Also, support for node.js versions 0.10 up to whatever the current 15 | latest is. Are those too tall an order? What's more, Bunyan is meant to work 16 | (at least partially) in the browser. Is there a good service for that? 17 | Please discuss on [issue #342](https://github.com/trentm/node-bunyan/issues/342). 18 | 19 | - Fielding issues labelled with "[Type-Question][Type-Question]", if you are familiar 20 | with Bunyan and know how to answer them, would be great. 21 | 22 | - If you want to dive into code, but aren't *that* familiar with node-bunyan, 23 | then [issues labelled with Experience-Easy][Experience-Easy] are a good 24 | place to start. 25 | 26 | - [Once I've made a once over 27 | triaging](https://github.com/trentm/node-bunyan/issues/335) and consolidating 28 | issues and PRs, volunteering for issues in a particular 29 | [component](#component) with which you have familiarity would be great. 30 | 31 | [Type-Question]: https://github.com/trentm/node-bunyan/issues?q=is%3Aopen+is%3Aissue+label%3AType-Question 32 | 33 | 34 | ## Trent's Biased Rules for Code 35 | 36 | In the hope that it makes it easier to get PRs into Bunyan, here is my biased 37 | list of what I typically want. Submitting a PR without all of these is 38 | *totally fine*! The only side-effect is that it may take longer for me to 39 | provide feedback on it and merge it. I'll politely request missing pieces. 40 | 41 | 42 | - Please follow existing code style. Contributed code must pass `make check`. 43 | (Note: I intended to [change to eslint 44 | soon](https://github.com/trentm/node-bunyan/issues/341), so currently `make 45 | check` might be a moving target.) 46 | 47 | - Any user visible change in behaviour should almost certainly include an 48 | update to the docs. Currently the "docs" is the README.md. 49 | 50 | - Adding a test case for code changes is **stronly recommended**, else I 51 | can't easily promise to not break your fix/feature later. If you don't 52 | grok the test suite, please ask. We can use it to form the basis for a 53 | "test/README.md". 54 | 55 | - Typically a code change should have an associated issue or PR. This allows 56 | addition of follow-up issues, discussion, test data, etc. to be associated 57 | with the commit. Just using GitHub pull requests makes this easy. 58 | 59 | - All but the most trivial code changes should have an addition to the 60 | [changelog](./CHANGES.md). The audience for the changelog is *Bunyan users*. 61 | However, because rebasing longer-lived PRs against master is a pain 62 | with a change to CHANGES.md, please **do not include a CHANGES.md change 63 | in your PR. Instead suggest a CHANGES.md addition in a comment on the 64 | PR.** 65 | 66 | - Good commit messages, please: 67 | - The first line should be a succinct summary of the issue or fix. A 68 | good candidate is to just cut 'n paste the issue title, if there is one. 69 | - If the commit is for a particular issue/PR (see previous rule), please 70 | list the issue number in the commit message. E.g. "Fixes #123" or "Related 71 | to #234". 72 | - The audience for commit messages is *Bunyan developers*. 73 | 74 | 75 | ## Pull Request Lifecycle 76 | 77 | (Language adapted from 78 | [terraform](https://github.com/hashicorp/terraform/blob/master/CONTRIBUTING.md).) 79 | 80 | - You are welcome to submit your pull request for commentary or review before it 81 | is fully completed. Please prefix the title of your pull request with "[WIP]" 82 | to indicate this. It's also a good idea to include specific questions or items 83 | you'd like feedback on. 84 | 85 | - Once you believe your pull request is ready to be merged, you can remove any 86 | "[WIP]" prefix from the title and a core team member will review. See 87 | Trent's Biased Rules above to help ensure that your contribution will be 88 | merged quickly. 89 | 90 | - Trent or, if things go well, a node-bunyan maintainer will look over your 91 | contribution and either provide comments letting you know if there is anything 92 | left to do. Please be patient. Unfortunately, I'm not able to carve out 93 | a *lot* of time for Bunyan development and maintenance. 94 | 95 | - Once all outstanding comments and checklist items have been addressed, your 96 | contribution will be merged. Merged PRs will be included in the next 97 | node-bunyan release. 98 | 99 | - In some cases, we might decide that a PR should be closed. We'll make sure to 100 | provide clear reasoning when this happens. 101 | 102 | 103 | ## Issue labels 104 | 105 | The point of issue labeling for node-bunyan is to help answer "what should be 106 | worked on now? what can be left for later?" I don't want issue labelling to 107 | become a burden for anyone, so (a) don't feel obliged to add them yourself and 108 | (b) I'm happy to reevaluate their usage. 109 | 110 | Bunyan shall have categories of [issue 111 | labels](https://github.com/trentm/node-bunyan/labels) named "$category-$value". 112 | An issue should have max *one* label from each set. Users of Google Code's 113 | dearly departed issue tracker may remember this kind of thing. This is a 114 | poorman's version of structured issue tracker metadata. 115 | 116 | I'm inclined to *not* do priorities right now. *Possibly* we'll use GitHub 117 | milestones to basically set targets for upcoming releases. But otherwise my 118 | sense is that for smaller OSS projects, assigning prios will get in the way. 119 | If people would think it helpful, I'd consider "Difficulty-" or "Experience-" 120 | categories (a la Rust's "E-" labels) to mark easier and intermediate tasks 121 | that someone interested but maybe not very familiar with Bunyan might want 122 | to tackle. 123 | 124 | For now, here are the various labels and their purpose: 125 | 126 | ### Meta 127 | 128 | - needstriage: Temporary label to help me do a single triage pass through all 129 | current open issues and PRs. 130 | See [#335](https://github.com/trentm/node-bunyan/issues/335) 131 | where I'm working through this. 132 | 133 | ### Type 134 | 135 | Color: green 136 | 137 | - Type-Unknown: If it is still unclear or undecided if an issue is an intended 138 | feature (perhaps arguing for better docs or examples to avoid confusion) or a 139 | bug, I will use this category. 140 | - Type-Question: Asking a question on Bunyan usage, about the project, etc. 141 | - Type-Bug: A bug in Bunyan's behaviour. 142 | - Type-Improvement: A new feature or other improvement. 143 | - Type-Doc: Issues with Bunyan's documentation. 144 | - Type-Task: A project task to be done. 145 | 146 | TODO: consider Type-Unknown for the "unclear if bug or feature" tickets. 147 | 148 | ### Component 149 | 150 | Color: blue 151 | 152 | - Component-Project: Project meta stuff like testing, linting, build, install, 153 | etc. 154 | - Component-CLI: The `bunyan` command-line tool. 155 | - Component-Lib: catch-all for other library stuff 156 | - Component-LibRotation: The bunyan library's log rotation support. 157 | - Component-LibBrowser: Bunyan's handling/support for running in the browser. 158 | - Component-LibFlush: A separate component for collecting the tickets related 159 | to closing/flushing bunyan streams on process shutdown. 160 | 161 | The point of components is to find like issues to help with reference, search 162 | and resolving them. If no component fits an issue/PR, then don't add a label. 163 | 164 | ### Resolution 165 | 166 | Color: red 167 | 168 | - Resolution-WontFix 169 | - Resolution-Duplicate 170 | - Resolution-Fixed: Also used to indicate "doc written", "question answered", 171 | "feature implemented". 172 | - Resolution-CannotRepro: After some reasonable attempt by maintainers to 173 | reproduce a bug report, I want it to be non-controversial to close it 174 | and mark it with this. If given more info by someone able to repro, we 175 | can happy re-open issues. 176 | 177 | ### Experience 178 | 179 | Color: yellow 180 | 181 | - Experience-Easy: Relatively little experience with node-bunyan should be 182 | required to complete this issue. 183 | - Experience-NeedsTest: Typically added to an issue or PR that needs a test 184 | case. Someone familiar enough with node-bunyan's test suite could tackle this. 185 | - Experience-Hard: At a guess, this is a thorny issue that requires known 186 | node-bunyan well, knowing node.js well, requires design review or all of 187 | these. 188 | 189 | One of the "Experience-\*" labels can optionally be put on an issue or PR to 190 | indicate what kind of experience a contributor would need with node-bunyan 191 | (and/or node.js) to complete it. For example, if you're looking for somewhere to 192 | start, check out the [Experience-Easy][Experience-Easy] tag. This category idea 193 | is borrowed from [rust's E-\* labels][rust-issue-triage]. 194 | 195 | [Experience-Easy]: https://github.com/trentm/node-bunyan/issues?q=is%3Aopen+is%3Aissue+label%3AExperience-Easy 196 | [rust-issue-triage]: https://github.com/rust-lang/rust/blob/master/CONTRIBUTING.md#issue-triage 197 | 198 | 199 | ## Acknowledgements 200 | 201 | Anything good about this document is thanks to inspiration from 202 | [rust](https://github.com/rust-lang/rust/blob/master/CONTRIBUTING.md) and, more 203 | recently 204 | [terraform](https://github.com/hashicorp/terraform/blob/master/CONTRIBUTING.md). 205 | Anything bad about it, is my fault. 206 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | # This is the MIT license 2 | 3 | Copyright 2016 Trent Mick 4 | Copyright 2016 Joyent Inc. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a 7 | copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included 15 | in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 18 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | SHELL := bash 3 | 4 | #---- Tools 5 | 6 | TAP_EXEC := ./node_modules/.bin/tap 7 | SUDO := sudo 8 | ifeq ($(shell uname -s),SunOS) 9 | # On SunOS (e.g. SmartOS) we expect to run the test suite as the 10 | # root user -- necessary to run dtrace. Therefore `pfexec` isn't 11 | # necessary. 12 | SUDO := 13 | endif 14 | DTRACE_UP_IN_HERE= 15 | ifeq ($(shell uname -s),SunOS) 16 | DTRACE_UP_IN_HERE=1 17 | endif 18 | ifeq ($(shell uname -s),Darwin) 19 | DTRACE_UP_IN_HERE=1 20 | endif 21 | 22 | 23 | #---- Files 24 | 25 | JSSTYLE_FILES := $(shell find lib test tools examples -name "*.js") bin/bunyan 26 | 27 | 28 | #---- Targets 29 | 30 | all $(TAP_EXEC): 31 | npm install $(NPM_INSTALL_FLAGS) 32 | 33 | # Ensure all version-carrying files have the same version. 34 | .PHONY: versioncheck 35 | versioncheck: 36 | @echo version is: $(shell node -e 'console.log(require("./package.json").version)') 37 | [[ `node -e 'console.log(require("./package.json").version)'` == `grep '^## ' CHANGES.md | head -2 | tail -1 | awk '{print $$2}'` ]] 38 | @echo Version check ok. 39 | 40 | .PHONY: cutarelease 41 | cutarelease: check 42 | [[ -z `git status --short` ]] # If this fails, the working dir is dirty. 43 | @which json 2>/dev/null 1>/dev/null && \ 44 | ver=$(shell json -f package.json version) && \ 45 | name=$(shell json -f package.json name) && \ 46 | publishedVer=$(shell npm view -j $(shell json -f package.json name)@$(shell json -f package.json version) version 2>/dev/null) && \ 47 | if [[ -n "$$publishedVer" ]]; then \ 48 | echo "error: $$name@$$ver is already published to npm"; \ 49 | exit 1; \ 50 | fi && \ 51 | echo "** Are you sure you want to tag and publish $$name@$$ver to npm?" && \ 52 | echo "** Enter to continue, Ctrl+C to abort." && \ 53 | read 54 | ver=$(shell cat package.json | json version) && \ 55 | date=$(shell date -u "+%Y-%m-%d") && \ 56 | git tag -a "$$ver" -m "version $$ver ($$date) beta" && \ 57 | git push --tags origin && \ 58 | npm publish --tag beta 59 | 60 | .PHONY: docs 61 | docs: toc 62 | @[[ `which ronn` ]] || (echo "No 'ronn' on your PATH. Install with 'gem install ronn'" && exit 2) 63 | mkdir -p man/man1 64 | ronn --style=toc --manual="bunyan manual" --date=$(shell git log -1 --pretty=format:%cd --date=short) --roff --html docs/bunyan.1.ronn 65 | 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)' 66 | 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)' 67 | @echo "# test with 'man ./docs/bunyan.1' and 'open ./docs/bunyan.1.html'" 68 | 69 | # Re-generate the README.md table of contents. 70 | toc: 71 | ./node_modules/.bin/markdown-toc -i README.md 72 | 73 | 74 | .PHONY: publish 75 | publish: 76 | mkdir -p tmp 77 | [[ -d tmp/bunyan-gh-pages ]] || git clone git@github.com:trentm/node-bunyan.git tmp/bunyan-gh-pages 78 | cd tmp/bunyan-gh-pages && git checkout gh-pages && git pull --rebase origin gh-pages 79 | cp docs/index.html tmp/bunyan-gh-pages/index.html 80 | cp docs/bunyan.1.html tmp/bunyan-gh-pages/bunyan.1.html 81 | (cd tmp/bunyan-gh-pages \ 82 | && git commit -a -m "publish latest docs" \ 83 | && git push origin gh-pages || true) 84 | 85 | .PHONY: distclean 86 | distclean: 87 | rm -rf node_modules 88 | 89 | 90 | #---- test 91 | 92 | .PHONY: test 93 | test: $(TAP_EXEC) 94 | test -z "$(DTRACE_UP_IN_HERE)" || test -n "$(SKIP_DTRACE)" || \ 95 | (node -e 'require("dtrace-provider").createDTraceProvider("isthisthingon")' && \ 96 | echo "\nNote: Use 'SKIP_DTRACE=1 make test' to skip parts of the test suite that require root." && \ 97 | $(SUDO) $(TAP_EXEC) test/dtrace/*.test.js) 98 | $(TAP_EXEC) test/*.test.js 99 | 100 | 101 | #---- check 102 | 103 | .PHONY: check-jsstyle 104 | check-jsstyle: $(JSSTYLE_FILES) 105 | ./tools/jsstyle -o indent=4,doxygen,unparenthesized-return=0,blank-after-start-comment=0,leading-right-paren-ok=1 $(JSSTYLE_FILES) 106 | 107 | .PHONY: check 108 | check: check-jsstyle versioncheck 109 | @echo "Check ok." 110 | 111 | .PHONY: prepush 112 | prepush: check testall 113 | @echo "Okay to push." 114 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # v2 2 | 3 | - `createLogger(, )` changes (#460) 4 | - see section below 5 | - the dtrace-provider thing (#487) 6 | TODO: answer Cody email 7 | - use package.json version for VERSION 8 | - use deps 9 | - dashdash 10 | - assert-plus? 11 | - verror? 12 | - break out to multiple files 13 | - want to work through PRs before that, so don't just break them all 14 | - TODO: a quick pass through tickets and pulls for other things to include 15 | - get ticket refs for the above, if any 16 | - formatters: read up again on `glp master..1.x` 17 | - support for customer formatters 18 | - for the CLI as well? How? ~/.bunyanrc? 19 | - if doing ~/.bunyanrc, then consider color schemes 20 | 21 | 22 | 23 | # changes to ctor and log.child to separate fields from config 24 | 25 | 26 | 27 | Current: 28 | 29 | createLogger() 30 | log.child(, ) 31 | 32 | Could be: 33 | 34 | createLogger(, ) 35 | log.child(, ) 36 | # Still support: log.child(, ) 37 | 38 | Pros: Compat issues are minimal: a change is only required if there is a 39 | collision with used field and a new config var name. 40 | Cons: A *slight* con is that my guess is the common usage of child is 41 | `log.child()`, so the more future-proof common usage becomes: 42 | 43 | log.child(null, ) 44 | 45 | That's not too bad. It is clearer at least than: 46 | 47 | log.child(, true) 48 | 49 | TODO: 50 | 51 | - is there a ticket for this work already? #460 52 | - make the change 53 | - do a migration guide? i.e. provide the grep commands to find all 54 | possible calls to inspect. E.g. if don't have `rg logUndefined` in your 55 | code, then you are fine. And one time future-proofing via changing 56 | to fields in the *second* arg. 57 | - list of issues/pulls that wanted to add new config fields 58 | 59 | 60 | # higher prio 61 | 62 | - published organized advice for 63 | https://github.com/trentm/node-bunyan/issues/37 so can close that out. 64 | Perhaps a wiki page with examples and strategies. 65 | - man page for the bunyan CLI (refer to it in the readme) 66 | - perhaps wait for a bunyan new version with deps, and use dashdash 67 | with a (vapour) man page generator 68 | 69 | 70 | # docs 71 | 72 | - document log.addStream() and log.addSerializers() 73 | 74 | 75 | # someday/maybe 76 | 77 | - 2.0 (?) with `v: 1` in log records. Fwd/bwd compat in `bunyan` CLI 78 | - `tail -f`-like support 79 | - full-on docs 80 | - better examples/ 81 | - better coloring 82 | - look at pino (bunyan style, perf benefits), also logpp (https://github.com/mrkmarron/logpp) 83 | - would be exciting to have bunyan support in http://lnav.org/ if that 84 | made sense 85 | - "template" support for 'rotating-file' stream to get dated rolled files 86 | - "all" or "off" levels? log4j? logging.py? 87 | logging.py has NOTSET === 0. I think that is only needed/used for 88 | multi-level hierarchical effective level. 89 | - buffered writes to increase speed: 90 | - I'd start with a tools/timeoutput.js for some numbers to compare 91 | before/after. Sustained high output to a file. 92 | - perhaps this would be a "buffered: true" option on the stream object 93 | - then wrap the "stream" with a local class that handles the buffering 94 | - to finish this, need the 'log.close' and `process.on('exit', ...)` 95 | work that Trent has started. 96 | - "canWrite" handling for full streams. Need to buffer a la log4js 97 | - test file log with logadm rotation: does it handle that? 98 | - test suite: 99 | - test for a cloned logger double-`stream.end()` causing problems. 100 | Perhaps the "closeOnExit" for existing streams should be false for 101 | clones. 102 | - test that a `log.clone(...)` adding a new field matching a serializer 103 | works *and* that an existing field in the parent is not *re-serialized*. 104 | - split out `bunyan` cli to a "bunyan" or "bunyan-reader" or "node-bunyan-reader" 105 | as the basis for tools to consume bunyan logs. It can grow indep of node-bunyan 106 | for generating the logs. 107 | It would take a Bunyan log record object and be expected to emit it. 108 | 109 | node-bunyan-reader 110 | .createReadStream(path, [options]) ? 111 | 112 | - coloring bug: in less the indented extra info lines only have the first 113 | line colored. Do we need the ANSI char on *each* line? That'll be 114 | slower. 115 | - document "well-known" keys from bunyan CLI p.o.v.. Add "client_req". 116 | - More `bunyan` output formats and filtering features. 117 | - Think about a bunyan dashboard that supports organizing and viewing logs 118 | from multiple hosts and services. 119 | - doc the restify RequestCaptureStream usage of RingBuffer. Great example. 120 | - A vim plugin (a la http://vim.cybermirror.org/runtime/autoload/zip.vim ?) to 121 | allow browsing (read-only) a bunyan log in rendered form. 122 | - Some speed comparisons with others to get a feel for Bunyan's speed. 123 | - what about promoting 'latency' field and making that easier? 124 | - `log.close` to close streams and shutdown and `this.closed` 125 | process.on('exit', log.close) 126 | -> 'end' for the name 127 | - bunyan cli: more layouts (http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/EnhancedPatternLayout.html) 128 | Custom log formats (in config file? in '-f' arg) using printf or hogan.js 129 | or whatever. Dap wants field width control for lining up. Hogan.js is 130 | probably overkill for this. 131 | - loggly example using raw streams, hook.io?, whatever. 132 | - serializer support: 133 | - restify-server.js example -> restifyReq ? or have `req` detect that. 134 | That is nicer for the "use all standard ones". *Does* restify req 135 | have anything special? 136 | - differential HTTP *client* req/res with *server* req/res. 137 | - statsd stream? http://codeascraft.etsy.com/2011/02/15/measure-anything-measure-everything/ 138 | Think about it. 139 | - web ui. Ideas: http://googlecloudplatform.blogspot.ca/2014/04/a-new-logs-viewer-for-google-cloud.html 140 | -------------------------------------------------------------------------------- /docs/bunyan.1: -------------------------------------------------------------------------------- 1 | .\" generated with Ronn/v0.7.3 2 | .\" http://github.com/rtomayko/ronn/tree/0.7.3 3 | . 4 | .TH "BUNYAN" "1" "January 2015" "" "bunyan manual" 5 | . 6 | .SH "NAME" 7 | \fBbunyan\fR \- filter and pretty\-print Bunyan log file content 8 | . 9 | .SH "SYNOPSIS" 10 | \fBbunyan\fR [OPTIONS] 11 | . 12 | .P 13 | \&\.\.\. | \fBbunyan\fR [OPTIONS] 14 | . 15 | .P 16 | \fBbunyan\fR [OPTIONS] \-p PID 17 | . 18 | .SH "DESCRIPTION" 19 | "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\. 20 | . 21 | .SS "Pretty\-printing" 22 | 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: 23 | . 24 | .IP "" 4 25 | . 26 | .nf 27 | 28 | $ bunyan foo\.log # or `cat foo\.log | bunyan 29 | [2012\-02\-08T22:56:52\.856Z] INFO: myservice/123 on example\.com: My message 30 | extra: multi 31 | line 32 | [2012\-02\-08T22:56:54\.856Z] ERROR: myservice/123 on example\.com: My message 33 | \.\.\. 34 | . 35 | .fi 36 | . 37 | .IP "" 0 38 | . 39 | .P 40 | By default the "long" output format is used\. Use the \fB\-o FORMAT\fR option to emit other formats\. E\.g\.: 41 | . 42 | .IP "" 4 43 | . 44 | .nf 45 | 46 | $ bunyan foo\.log \-o short 47 | 22:56:52\.856Z INFO myservice: My message 48 | extra: multi 49 | line 50 | 22:56:54\.856Z ERROR myservice: My message 51 | \.\.\. 52 | . 53 | .fi 54 | . 55 | .IP "" 0 56 | . 57 | .P 58 | These will color the output if supported in your terminal\. See "OUTPUT FORMATS" below\. 59 | . 60 | .SS "Filtering" 61 | The \fBbunyan\fR CLI can also be used to filter a bunyan log\. Use \fB\-l LEVEL\fR to filter by level: 62 | . 63 | .IP "" 4 64 | . 65 | .nf 66 | 67 | $ bunyan foo\.log \-l error # show only \'error\' level records 68 | [2012\-02\-08T22:56:54\.856Z] ERROR: myservice/123 on example\.com: My message 69 | . 70 | .fi 71 | . 72 | .IP "" 0 73 | . 74 | .P 75 | 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: 76 | . 77 | .IP "" 4 78 | . 79 | .nf 80 | 81 | $ bunyan foo\.log \-c `this\.three` # show records with the \'extra\' field 82 | [2012\-02\-08T22:56:52\.856Z] INFO: myservice/123 on example\.com: My message 83 | extra: multi 84 | line 85 | . 86 | .fi 87 | . 88 | .IP "" 0 89 | . 90 | .SH "OPTIONS" 91 | . 92 | .TP 93 | \fB\-h\fR, \fB\-\-help\fR 94 | Print this help info and exit\. 95 | . 96 | .TP 97 | \fB\-\-version\fR 98 | Print version of this command and exit\. 99 | . 100 | .TP 101 | \fB\-q\fR, \fB\-\-quiet\fR 102 | Don\'t warn if input isn\'t valid JSON\. 103 | . 104 | .P 105 | Dtrace options (only on dtrace\-supporting platforms): 106 | . 107 | .TP 108 | \fB\-p PID\fR, \fB\-p NAME\fR 109 | 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\'\. 110 | . 111 | .P 112 | Filtering options: 113 | . 114 | .TP 115 | \fB\-l\fR, \fB\-\-level LEVEL\fR 116 | Only show messages at or above the specified level\. You can specify level \fInames\fR or numeric values\. (See \'Log Levels\' below\.) 117 | . 118 | .TP 119 | \fB\-c COND\fR, \fB\-\-condition COND\fR 120 | 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\. 121 | . 122 | .TP 123 | \fB\-\-strict\fR 124 | Suppress all but legal Bunyan JSON log lines\. By default non\-JSON, and non\-Bunyan lines are passed through\. 125 | . 126 | .P 127 | Output options: 128 | . 129 | .TP 130 | \fB\-\-color\fR 131 | Colorize output\. Defaults to try if output stream is a TTY\. 132 | . 133 | .TP 134 | \fB\-\-no\-color\fR 135 | Force no coloring (e\.g\. terminal doesn\'t support it) 136 | . 137 | .TP 138 | \fB\-o FORMAT\fR, \fB\-\-output FORMAT\fR 139 | 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\. 140 | . 141 | .TP 142 | \fB\-j\fR 143 | Shortcut for \fB\-o json\fR\. 144 | . 145 | .TP 146 | \fB\-L\fR, \fB\-\-time local\fR 147 | Display the time field in \fIlocal\fR time, rather than the default UTC time\. 148 | . 149 | .SH "LOG LEVELS" 150 | 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\. 151 | . 152 | .IP "" 4 153 | . 154 | .nf 155 | 156 | Level Name Level Number Symbol in COND Scripts 157 | trace 10 TRACE 158 | debug 20 DEBUG 159 | info 30 INFO 160 | warn 40 WARN 161 | error 50 ERROR 162 | fatal 60 FATAL 163 | . 164 | .fi 165 | . 166 | .IP "" 0 167 | . 168 | .SH "OUTPUT FORMATS" 169 | . 170 | .nf 171 | 172 | FORMAT NAME DESCRIPTION 173 | long (default) The default output\. Long form\. Colored and "pretty"\. 174 | \'req\' and \'res\' and \'err\' fields are rendered specially 175 | as an HTTP request, HTTP response and exception 176 | stack trace, respectively\. For backward compat, the 177 | name "paul" also works for this\. 178 | short Like the default output, but more concise\. Some 179 | typically redundant fields are ellided\. 180 | json JSON output, 2\-space indentation\. 181 | json\-N JSON output, N\-space indentation, e\.g\. "json\-4" 182 | bunyan Alias for "json\-0", the Bunyan "native" format\. 183 | inspect Node\.js `util\.inspect` output\. 184 | . 185 | .fi 186 | . 187 | .SH "DTRACE SUPPORT" 188 | 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: 189 | . 190 | .IP "" 4 191 | . 192 | .nf 193 | 194 | log\-trace 195 | log\-debug 196 | log\-info 197 | log\-warn 198 | log\-error 199 | log\-fatal 200 | . 201 | .fi 202 | . 203 | .IP "" 0 204 | . 205 | .P 206 | 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\. 207 | . 208 | .P 209 | See \fIhttps://github\.com/trentm/node\-bunyan#dtrace\-support\fR for more details and the \'\-p PID\' option above for convenience usage\. 210 | . 211 | .SH "ENVIRONMENT" 212 | . 213 | .TP 214 | \fBBUNYAN_NO_COLOR\fR 215 | Set to a non\-empty value to force no output coloring\. See \'\-\-no\-color\'\. 216 | . 217 | .SH "PROJECT & BUGS" 218 | \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"\. 219 | . 220 | .IP "\(bu" 4 221 | README, Install notes: \fIhttps://github\.com/trentm/node\-bunyan#readme\fR 222 | . 223 | .IP "\(bu" 4 224 | Report bugs to \fIhttps://github\.com/trentm/node\-bunyan/issues\fR\. 225 | . 226 | .IP "\(bu" 4 227 | See the full changelog at: \fIhttps://github\.com/trentm/node\-bunyan/blob/master/CHANGES\.md\fR 228 | . 229 | .IP "" 0 230 | . 231 | .SH "LICENSE" 232 | MIT License (see \fIhttps://github\.com/trentm/node\-bunyan/blob/master/LICENSE\.txt\fR) 233 | . 234 | .SH "COPYRIGHT" 235 | node\-bunyan is Copyright (c) 2012 Joyent, Inc\. Copyright (c) 2012 Trent Mick\. All rights reserved\. 236 | -------------------------------------------------------------------------------- /docs/bunyan.1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | bunyan(1) - filter and pretty-print Bunyan log file content 7 | 44 | 50 | 51 | 58 | 59 |
60 | 61 | 74 | 75 |
    76 |
  1. bunyan(1)
  2. 77 |
  3. bunyan manual
  4. 78 |
  5. bunyan(1)
  6. 79 |
80 | 81 |

NAME

82 |

83 | bunyan - filter and pretty-print Bunyan log file content 84 |

85 | 86 |

SYNOPSIS

87 | 88 |

bunyan [OPTIONS]

89 | 90 |

... | bunyan [OPTIONS]

91 | 92 |

bunyan [OPTIONS] -p PID

93 | 94 |

DESCRIPTION

95 | 96 |

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

99 | 100 |

Pretty-printing

101 | 102 |

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

105 | 106 |
$ bunyan foo.log          # or `cat foo.log | bunyan
107 | [2012-02-08T22:56:52.856Z]  INFO: myservice/123 on example.com: My message
108 |     extra: multi
109 |     line
110 | [2012-02-08T22:56:54.856Z] ERROR: myservice/123 on example.com: My message
111 | ...
112 | 
113 | 114 |

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

116 | 117 |
$ bunyan foo.log -o short
118 | 22:56:52.856Z  INFO myservice: My message
119 |     extra: multi
120 |     line
121 | 22:56:54.856Z ERROR myservice: My message
122 | ...
123 | 
124 | 125 |

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

127 | 128 |

Filtering

129 | 130 |

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

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

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

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

OPTIONS

147 | 148 |
149 |
-h, --help

Print this help info and exit.

150 |
--version

Print version of this command and exit.

151 |
-q, --quiet

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

152 |
153 | 154 | 155 |

Dtrace options (only on dtrace-supporting platforms):

156 | 157 |
158 |
-p PID, -p NAME
Process bunyan:log-* probes from the process with the given PID. 159 | Can be used multiple times, or specify all processes with '*', 160 | or a set of processes whose command & args match a pattern with 161 | '-p NAME'.
162 |
163 | 164 | 165 |

Filtering options:

166 | 167 |
168 |
-l, --level LEVEL

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

170 |
-c COND, --condition COND

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

172 |
--strict

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

174 |
175 | 176 | 177 |

Output options:

178 | 179 |
180 |
--color

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

181 |
--no-color

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

182 |
-o FORMAT, --output FORMAT

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

184 |
-j

Shortcut for -o json.

185 |
-L, --time local

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

187 |
188 | 189 | 190 |

LOG LEVELS

191 | 192 |

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

195 | 196 |
Level Name      Level Number    Symbol in COND Scripts
197 | trace           10              TRACE
198 | debug           20              DEBUG
199 | info            30              INFO
200 | warn            40              WARN
201 | error           50              ERROR
202 | fatal           60              FATAL
203 | 
204 | 205 |

OUTPUT FORMATS

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

DTRACE SUPPORT

222 | 223 |

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

226 | 227 |
log-trace
228 | log-debug
229 | log-info
230 | log-warn
231 | log-error
232 | log-fatal
233 | 
234 | 235 |

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

239 | 240 |

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

242 | 243 |

ENVIRONMENT

244 | 245 |
246 |
BUNYAN_NO_COLOR
Set to a non-empty value to force no output coloring. See '--no-color'.
247 |
248 | 249 | 250 |

PROJECT & BUGS

251 | 252 |

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

255 | 256 | 261 | 262 | 263 |

LICENSE

264 | 265 |

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

266 | 267 | 268 | 269 |

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

271 | 272 | 273 |
    274 |
  1. 275 |
  2. January 2015
  3. 276 |
  4. bunyan(1)
  5. 277 |
278 | 279 |
280 | Fork me on GitHub 281 | 282 | -------------------------------------------------------------------------------- /docs/bunyan.1.ronn: -------------------------------------------------------------------------------- 1 | # bunyan(1) -- filter and pretty-print Bunyan log file content 2 | 3 | 4 | ## SYNOPSIS 5 | 6 | `bunyan` \[OPTIONS\] 7 | 8 | ... | `bunyan` \[OPTIONS\] 9 | 10 | `bunyan` \[OPTIONS\] -p PID 11 | 12 | 13 | ## DESCRIPTION 14 | 15 | "Bunyan" is **a simple and fast a JSON logging library** for node.js services, 16 | a one-JSON-object-per-line log format, and **a `bunyan` CLI tool** for nicely 17 | viewing those logs. This man page describes the latter. 18 | 19 | 20 | ### Pretty-printing 21 | 22 | A bunyan log file is a stream of JSON objects, optionally interspersed with 23 | non-JSON log lines. The primary usage of bunyan(1) is to pretty print, 24 | for example: 25 | 26 | $ bunyan foo.log # or `cat foo.log | bunyan 27 | [2012-02-08T22:56:52.856Z] INFO: myservice/123 on example.com: My message 28 | extra: multi 29 | line 30 | [2012-02-08T22:56:54.856Z] ERROR: myservice/123 on example.com: My message 31 | ... 32 | 33 | By default the "long" output format is used. Use the `-o FORMAT` option to 34 | emit other formats. E.g.: 35 | 36 | $ bunyan foo.log -o short 37 | 22:56:52.856Z INFO myservice: My message 38 | extra: multi 39 | line 40 | 22:56:54.856Z ERROR myservice: My message 41 | ... 42 | 43 | These will color the output if supported in your terminal. 44 | See "OUTPUT FORMATS" below. 45 | 46 | 47 | ### Filtering 48 | 49 | The `bunyan` CLI can also be used to filter a bunyan log. Use `-l LEVEL` 50 | to filter by level: 51 | 52 | $ bunyan foo.log -l error # show only 'error' level records 53 | [2012-02-08T22:56:54.856Z] ERROR: myservice/123 on example.com: My message 54 | 55 | Use `-c COND` to filter on a JavaScript expression returning true on the 56 | record data. In the COND code, `this` refers to the record object: 57 | 58 | $ bunyan foo.log -c `this.three` # show records with the 'extra' field 59 | [2012-02-08T22:56:52.856Z] INFO: myservice/123 on example.com: My message 60 | extra: multi 61 | line 62 | 63 | 64 | ## OPTIONS 65 | 66 | * `-h`, `--help`: 67 | Print this help info and exit. 68 | 69 | * `--version`: 70 | Print version of this command and exit. 71 | 72 | * `-q`, `--quiet`: 73 | Don't warn if input isn't valid JSON. 74 | 75 | Dtrace options (only on dtrace-supporting platforms): 76 | 77 | * `-p PID`, `-p NAME`: 78 | Process bunyan:log-\* probes from the process with the given PID. 79 | Can be used multiple times, or specify all processes with '\*', 80 | or a set of processes whose command & args match a pattern with 81 | '-p NAME'. 82 | 83 | Filtering options: 84 | 85 | * `-l`, `--level LEVEL`: 86 | Only show messages at or above the specified level. You can specify level 87 | *names* or numeric values. (See 'Log Levels' below.) 88 | 89 | * `-c COND`, `--condition COND`: 90 | Run each log message through the condition and only show those that 91 | resolve to a truish value. E.g. `-c 'this.pid == 123'`. 92 | 93 | * `--strict`: 94 | Suppress all but legal Bunyan JSON log lines. By default non-JSON, and 95 | non-Bunyan lines are passed through. 96 | 97 | Output options: 98 | 99 | * `--color`: 100 | Colorize output. Defaults to try if output stream is a TTY. 101 | 102 | * `--no-color`: 103 | Force no coloring (e.g. terminal doesn't support it) 104 | 105 | * `-o FORMAT`, `--output FORMAT`: 106 | Specify an output format. One of `long` (the default), `short`, `json`, 107 | `json-N`, `bunyan` (the native bunyan 0-indent JSON output) or `inspect`. 108 | 109 | * `-j`: 110 | Shortcut for `-o json`. 111 | 112 | * `-L`, `--time local`: 113 | Display the time field in *local* time, rather than the default UTC 114 | time. 115 | 116 | 117 | ## LOG LEVELS 118 | 119 | In Bunyan log records, then `level` field is a number. For the `-l|--level` 120 | argument the level **names** are supported as shortcuts. In `-c|--condition` 121 | scripts, uppercase symbols like "DEBUG" are defined for convenience. 122 | 123 | Level Name Level Number Symbol in COND Scripts 124 | trace 10 TRACE 125 | debug 20 DEBUG 126 | info 30 INFO 127 | warn 40 WARN 128 | error 50 ERROR 129 | fatal 60 FATAL 130 | 131 | 132 | ## OUTPUT FORMATS 133 | 134 | FORMAT NAME DESCRIPTION 135 | long (default) The default output. Long form. Colored and "pretty". 136 | 'req' and 'res' and 'err' fields are rendered specially 137 | as an HTTP request, HTTP response and exception 138 | stack trace, respectively. For backward compat, the 139 | name "paul" also works for this. 140 | short Like the default output, but more concise. Some 141 | typically redundant fields are ellided. 142 | json JSON output, 2-space indentation. 143 | json-N JSON output, N-space indentation, e.g. "json-4" 144 | bunyan Alias for "json-0", the Bunyan "native" format. 145 | inspect Node.js `util.inspect` output. 146 | 147 | 148 | ## DTRACE SUPPORT 149 | 150 | On systems that support DTrace (e.g., MacOS, FreeBSD, illumos derivatives 151 | like SmartOS and OmniOS), Bunyan will create a DTrace provider (`bunyan`) 152 | that makes available the following probes: 153 | 154 | log-trace 155 | log-debug 156 | log-info 157 | log-warn 158 | log-error 159 | log-fatal 160 | 161 | Each of these probes has a single argument: the string that would be 162 | written to the log. Note that when a probe is enabled, it will 163 | fire whenever the corresponding function is called, even if the level of 164 | the log message is less than that of any stream. 165 | 166 | See for more details 167 | and the '-p PID' option above for convenience usage. 168 | 169 | 170 | ## ENVIRONMENT 171 | 172 | * `BUNYAN_NO_COLOR`: 173 | Set to a non-empty value to force no output coloring. See '--no-color'. 174 | 175 | 176 | ## PROJECT & BUGS 177 | 178 | `bunyan` is written in JavaScript and requires node.js (`node`). The project 179 | lives at and is published to npm as 180 | "bunyan". 181 | 182 | * README, Install notes: 183 | * Report bugs to . 184 | * See the full changelog at: 185 | 186 | 187 | ## LICENSE 188 | 189 | MIT License (see ) 190 | 191 | 192 | ## COPYRIGHT 193 | 194 | node-bunyan is Copyright (c) 2012 Joyent, Inc. Copyright (c) 2012 Trent Mick. 195 | All rights reserved. 196 | -------------------------------------------------------------------------------- /docs/img/bunyan.browserify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trentm/node-bunyan/5c2258ecb1d33ba34bd7fbd6167e33023dc06e40/docs/img/bunyan.browserify.png -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | bunyan(1) man page 2 | -------------------------------------------------------------------------------- /examples/err.js: -------------------------------------------------------------------------------- 1 | // Example logging an error: 2 | 3 | var http = require('http'); 4 | var bunyan = require('../lib/bunyan'); 5 | var util = require('util'); 6 | 7 | var log = bunyan.createLogger({ 8 | name: 'myserver', 9 | serializers: { 10 | err: bunyan.stdSerializers.err, // <--- use this 11 | } 12 | }); 13 | 14 | try { 15 | throw new TypeError('boom'); 16 | } catch (err) { 17 | log.warn({err: err}, 'operation went boom: %s', err) // <--- here 18 | } 19 | 20 | log.info(new TypeError('how about this?')) // <--- alternatively this 21 | 22 | 23 | try { 24 | throw 'boom string'; 25 | } catch (err) { 26 | log.error(err) 27 | } 28 | 29 | /* BEGIN JSSTYLED */ 30 | /** 31 | * 32 | * $ node err.js | ../bin/bunyan -j 33 | * { 34 | * "name": "myserver", 35 | * "hostname": "banana.local", 36 | * "err": { 37 | * "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)", 38 | * "name": "TypeError", 39 | * "message": "boom" 40 | * }, 41 | * "level": 4, 42 | * "msg": "operation went boom: TypeError: boom", 43 | * "time": "2012-02-02T04:42:53.206Z", 44 | * "v": 0 45 | * } 46 | * $ node err.js | ../bin/bunyan 47 | * [2012-02-02T05:02:39.412Z] WARN: myserver on banana.local: operation went boom: TypeError: boom 48 | * TypeError: boom 49 | * at Object. (/Users/trentm/tm/node-bunyan/examples/err.js:15:9) 50 | * at Module._compile (module.js:411:26) 51 | * at Object..js (module.js:417:10) 52 | * at Module.load (module.js:343:31) 53 | * at Function._load (module.js:302:12) 54 | * at Array.0 (module.js:430:10) 55 | * at EventEmitter._tickCallback (node.js:126:26) 56 | * 57 | */ 58 | /* END JSSTYLED */ 59 | -------------------------------------------------------------------------------- /examples/handle-fs-error.js: -------------------------------------------------------------------------------- 1 | // Example handling an fs error for a Bunyan-created 2 | // stream: we create a logger to a file that is read-only. 3 | 4 | var fs = require('fs'); 5 | var path = require('path'); 6 | var bunyan = require('../lib/bunyan'); 7 | 8 | var FILENAME = 'handle-fs-error.log'; 9 | var S_IWUSR = 00200; // mask for owner write permission in stat mode 10 | 11 | console.warn('- Log file is "%s".', FILENAME); 12 | if (!path.existsSync(FILENAME)) { 13 | console.warn('- Touch log file.'); 14 | fs.writeFileSync(FILENAME, 'touch\n'); 15 | } 16 | if (fs.statSync(FILENAME).mode & S_IWUSR) { 17 | console.warn('- Make log file read-only.'); 18 | fs.chmodSync(FILENAME, 0444); 19 | } 20 | 21 | console.warn('- Create logger.'); 22 | var log = bunyan.createLogger({ 23 | name: 'handle-fs-error', 24 | streams: [ 25 | {path: FILENAME} 26 | ] 27 | }); 28 | 29 | log.on('error', function (err) { 30 | console.warn('- The logger emitted an error:', err); 31 | }); 32 | 33 | console.warn('- Call log.info(...).'); 34 | log.info('info log message'); 35 | console.warn('- Called log.info(...).'); 36 | 37 | setTimeout(function () { 38 | console.warn('- Call log.warn(...).'); 39 | log.warn('warn log message'); 40 | console.warn('- Called log.warn(...).'); 41 | }, 1000); 42 | -------------------------------------------------------------------------------- /examples/hi.js: -------------------------------------------------------------------------------- 1 | var bunyan = require('../lib/bunyan'); 2 | 3 | // Basic usage. 4 | var log = bunyan.createLogger({name: 'myapp', level: 'info', src: true}); 5 | 6 | // isInfoEnabled replacement 7 | console.log('log.info() is:', log.info()) 8 | 9 | // `util.format`-based printf handling 10 | log.info('hi'); 11 | log.info('hi', 'trent'); 12 | log.info('hi %s there', true); 13 | 14 | // First arg as an object adds fields to the log record. 15 | log.info({foo:'bar', multiline:'one\ntwo\nthree'}, 'hi %d', 1, 'two', 3); 16 | 17 | 18 | // Shows `log.child(...)` to specialize a logger for a sub-component. 19 | console.log('\n') 20 | 21 | function Wuzzle(options) { 22 | this.log = options.log; 23 | this.log.info('creating a wuzzle') 24 | } 25 | 26 | Wuzzle.prototype.woos = function () { 27 | this.log.warn('This wuzzle is woosey.') 28 | } 29 | 30 | var wuzzle = new Wuzzle({log: log.child({component: 'wuzzle'})}); 31 | wuzzle.woos(); 32 | log.info('done with the wuzzle') 33 | -------------------------------------------------------------------------------- /examples/level.js: -------------------------------------------------------------------------------- 1 | // Play with setting levels. 2 | // 3 | // TODO: put this in a damn test suite 4 | 5 | var bunyan = require('../lib/bunyan'), 6 | DEBUG = bunyan.DEBUG, 7 | INFO = bunyan.INFO, 8 | WARN = bunyan.WARN; 9 | var assert = require('assert'); 10 | 11 | // Basic usage. 12 | var log = bunyan.createLogger({ 13 | name: 'example-level', 14 | streams: [ 15 | { 16 | name: 'stdout', 17 | stream: process.stdout, 18 | level: 'debug' 19 | }, 20 | { 21 | name: 'stderr', 22 | stream: process.stderr 23 | } 24 | ] 25 | }); 26 | 27 | assert.equal(log.level(), DEBUG); 28 | assert.equal(log.levels()[0], DEBUG); 29 | assert.equal(log.levels()[1], INFO); 30 | assert.equal(log.levels(0), DEBUG); 31 | assert.equal(log.levels(1), INFO); 32 | 33 | assert.equal(log.levels('stdout'), DEBUG) 34 | try { 35 | log.levels('foo') 36 | } catch (e) { 37 | assert.ok(e.message.indexOf('name') !== -1) 38 | } 39 | 40 | log.trace('no one should see this') 41 | log.debug('should see this once (on stdout)') 42 | log.info('should see this twice') 43 | log.levels('stdout', INFO) 44 | log.debug('no one should see this either') 45 | log.level('trace') 46 | log.trace('should see this twice as 4th and 5th emitted log messages') 47 | -------------------------------------------------------------------------------- /examples/log-undefined-values.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* BEGIN JSSTYLED */ 3 | /** 4 | * is a change to add a 5 | * feature to Bunyan's log record stringification to log `undefined` values. 6 | * Let's attempt that with a custom raw stream. 7 | * 8 | * Note that a raw stream here isn't ideal, because using a custom raw stream 9 | * means that it is a pain to use some of the other built-in stream types 10 | * (file, rotating-file). However, it might be a satisfactory workaround for 11 | * some. 12 | * 13 | * Example: 14 | * $ node log-undefined-values.js 15 | * {"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} 16 | * $ node log-undefined-values.js | bunyan 17 | * [2017-03-04T20:54:41.874Z] INFO: log-undefined-values/28194 on danger0.local: hi (anull=null, aundef=[Undefined], anum=42, astr=foo) 18 | */ 19 | /* END JSSTYLED */ 20 | 21 | var bunyan = require('../lib/bunyan'); 22 | var fs = require('fs'); 23 | 24 | 25 | function replacer() { 26 | // Note: If node > 0.10, then could use Set here (see `safeCyclesSet()` 27 | // in bunyan.js) for a performance improvement. 28 | var seen = []; 29 | return function (key, val) { 30 | if (val === undefined) { 31 | return '[Undefined]'; 32 | } else if (!val || typeof (val) !== 'object') { 33 | return val; 34 | } 35 | if (seen.indexOf(val) !== -1) { 36 | return '[Circular]'; 37 | } 38 | seen.push(val); 39 | return val; 40 | }; 41 | } 42 | 43 | function LogUndefinedValuesStream(stream) { 44 | this.stream = stream; 45 | } 46 | LogUndefinedValuesStream.prototype.write = function (rec) { 47 | var str = JSON.stringify(rec, replacer()) + '\n'; 48 | this.stream.write(str); 49 | } 50 | 51 | var log = bunyan.createLogger({ 52 | name: 'log-undefined-values', 53 | streams: [ { 54 | level: 'info', 55 | type: 'raw', 56 | stream: new LogUndefinedValuesStream(process.stdout) 57 | } ] 58 | }); 59 | 60 | log.info({anull: null, aundef: undefined, anum: 42, astr: 'foo'}, 'hi'); 61 | -------------------------------------------------------------------------------- /examples/long-running.js: -------------------------------------------------------------------------------- 1 | /* 2 | * A long-running process that does some periodic logging. Use bunyan with 3 | * it some of these ways: 4 | * 5 | * 1. Direct piping: 6 | * node long-running.js | bunyan 7 | * 2. Logging to file (e.g. if run via a service system like upstart or 8 | * illumos' SMF that sends std output to a log file), then tail -f that 9 | * log file. 10 | * node long-running.js > long-running.log 2>&1 11 | * tail -f long-running.log | bunyan 12 | * 3. Dtrace to watch the logging. This has the bonus of being able to watch 13 | * all log levels... even if not normally emitted. 14 | * node long-running.js > long-running.log 2>&1 15 | * bunyan -p $(head -1 long-running.log | json pid) 16 | * 17 | */ 18 | 19 | var fs = require('fs'); 20 | var bunyan = require('../lib/bunyan'); 21 | 22 | 23 | function randint(n) { 24 | return Math.floor(Math.random() * n); 25 | } 26 | 27 | function randchoice(array) { 28 | return array[randint(array.length)]; 29 | } 30 | 31 | 32 | //---- mainline 33 | 34 | var words = fs.readFileSync( 35 | __dirname + '/long-running.js', 'utf8').split(/\s+/); 36 | var levels = ['trace', 'debug', 'info', 'warn', 'error', 'fatal']; 37 | var timeout; 38 | 39 | var log = bunyan.createLogger({name: 'lr', level: 'debug'}); 40 | 41 | // We're logging to stdout. Let's exit gracefully on EPIPE. E.g. if piped 42 | // to `head` which will close after N lines. 43 | process.stdout.on('error', function (err) { 44 | if (err.code === 'EPIPE') { 45 | process.exit(0); 46 | } 47 | }) 48 | 49 | function logOne() { 50 | var level = randchoice(levels); 51 | var msg = [randchoice(words), randchoice(words)].join(' '); 52 | var delay = randint(300); 53 | //console.warn('long-running about to log.%s(..., "%s")', level, msg) 54 | log[level]({'word': randchoice(words), 'delay': delay}, msg); 55 | timeout = setTimeout(logOne, delay); 56 | } 57 | 58 | log.info('hi, this is the start'); 59 | timeout = setTimeout(logOne, 1000); 60 | -------------------------------------------------------------------------------- /examples/multi.js: -------------------------------------------------------------------------------- 1 | var bunyan = require('../lib/bunyan'); 2 | log = bunyan.createLogger({ 3 | name: 'amon', 4 | streams: [ 5 | { 6 | level: 'info', 7 | stream: process.stdout, 8 | }, 9 | { 10 | level: 'error', 11 | path: 'multi.log' 12 | } 13 | ] 14 | }); 15 | 16 | 17 | log.debug('hi nobody on debug'); 18 | log.info('hi stdout on info'); 19 | log.error('hi both on error'); 20 | log.fatal('hi both on fatal'); 21 | -------------------------------------------------------------------------------- /examples/mute-by-envvars-stream.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Example of a MuteByEnvVars Bunyan stream to mute log records matching some 3 | * envvars. I.e. as a way to do: 4 | * https://github.com/trentm/node-bunyan/issues/175 5 | * https://github.com/trentm/node-bunyan/pull/176 6 | * outside of core. 7 | * 8 | * Usage: 9 | * $ node mute-by-envvars-stream.js 10 | * {"name":"mute-by-envvars-stream",...,"msg":"hi raw stream"} 11 | * {"name":"mute-by-envvars-stream",...,"foo":"bar","msg":"added \"foo\" key"} 12 | * 13 | * $ BUNYAN_MUTE_foo=bar node mute-by-envvars-stream.js 14 | * {"name":"mute-by-envvars-stream",...,"msg":"hi raw stream"} 15 | * 16 | * Dev Notes: 17 | * - This currently treats all 'BUNYAN_MUTE_foo=bar' envvar values as strings. 18 | * That might not be desired. 19 | * - This is a quick implementation: inefficient and not well tested. 20 | * - Granted that Bunyan streams are hard to compose. For example, using 21 | * `MuteByEnvVars` to be a filter before writing logs to a *file* is a pain 22 | * for the file open/close handling. It would be nicer if Bunyan had a 23 | * pipeline of "filters" (more like core node.js streams). 24 | */ 25 | 26 | var bunyan = require('../lib/bunyan'); 27 | 28 | 29 | function MuteByEnvVars(opts) { 30 | opts = opts || {}; 31 | this.stream = opts.stream || process.stdout; 32 | 33 | var PREFIX = 'BUNYAN_MUTE_'; 34 | 35 | // Process the env once. 36 | this.mutes = {}; 37 | for (k in process.env) { 38 | if (k.indexOf(PREFIX) === 0) { 39 | this.mutes[k.slice(PREFIX.length)] = process.env[k]; 40 | } 41 | } 42 | } 43 | 44 | /** 45 | * Returns the given object's "o" property named by "s" using the dot notation. 46 | * 47 | * this({ name: { first: "value" } }, name.first) == "value" 48 | * 49 | * This is a verbatin copy of http://stackoverflow.com/a/6491621/433814 50 | * 51 | * @param o {object} is an object. 52 | * @param s (string} is the string in the "dot" notation. 53 | */ 54 | MuteByEnvVars.prototype._objectFromDotNotation = function (o, s) { 55 | s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties 56 | s = s.replace(/^\./, ''); // strip leading dot 57 | var a = s.split('.'); 58 | while (a.length) { 59 | var n = a.shift(); 60 | if (n in o) { 61 | o = o[n]; 62 | } else { 63 | return; 64 | } 65 | } 66 | return o; 67 | } 68 | 69 | MuteByEnvVars.prototype.write = function (rec) { 70 | if (typeof (rec) !== 'object') { 71 | console.error('error: MuteByEnvVars raw stream got a non-object ' 72 | + 'record: %j', rec); 73 | return; 74 | } 75 | 76 | var muteRec = false; 77 | var keys = Object.keys(this.mutes); 78 | for (var i = 0; i < keys.length; i++) { 79 | var k = keys[i]; 80 | var match = this._objectFromDotNotation(rec, k); 81 | if (match === this.mutes[k]) { 82 | muteRec = true; 83 | break; 84 | } 85 | } 86 | if (!muteRec) { 87 | this.stream.write(JSON.stringify(rec) + '\n'); 88 | } 89 | } 90 | 91 | 92 | 93 | // ---- example usage of the MuteByEnvVars stream 94 | 95 | var log = bunyan.createLogger({ 96 | name: 'mute-by-envvars-stream', 97 | streams: [ 98 | { 99 | level: 'info', 100 | stream: new MuteByEnvVars(), 101 | type: 'raw' 102 | }, 103 | ] 104 | }); 105 | 106 | 107 | log.info('hi raw stream'); 108 | log.info({foo: 'bar'}, 'added "foo" key'); 109 | -------------------------------------------------------------------------------- /examples/raw-stream.js: -------------------------------------------------------------------------------- 1 | // Example of a "raw" stream in a Bunyan Logger. A raw stream is one to 2 | // which log record *objects* are written instead of the JSON-serialized 3 | // string. 4 | 5 | var bunyan = require('../lib/bunyan'); 6 | 7 | 8 | /** 9 | * A raw Bunyan Logger stream object. It takes raw log records and writes 10 | * them to stdout with an added "yo": "yo" field. 11 | */ 12 | function MyRawStream() {} 13 | MyRawStream.prototype.write = function (rec) { 14 | if (typeof (rec) !== 'object') { 15 | console.error('error: raw stream got a non-object record: %j', rec) 16 | } else { 17 | rec.yo = 'yo'; 18 | process.stdout.write(JSON.stringify(rec) + '\n'); 19 | } 20 | } 21 | 22 | 23 | // A Logger using the raw stream. 24 | var log = bunyan.createLogger({ 25 | name: 'raw-example', 26 | streams: [ 27 | { 28 | level: 'info', 29 | stream: new MyRawStream(), 30 | type: 'raw' 31 | }, 32 | ] 33 | }); 34 | 35 | 36 | log.info('hi raw stream'); 37 | log.info({foo: 'bar'}, 'added "foo" key'); 38 | -------------------------------------------------------------------------------- /examples/ringbuffer.js: -------------------------------------------------------------------------------- 1 | /* Create a ring buffer that stores the last 100 records. */ 2 | var bunyan = require('..'); 3 | var ringbuffer = new bunyan.RingBuffer({ limit: 100 }); 4 | var log = new bunyan({ 5 | name: 'foo', 6 | streams: [ { 7 | type: 'raw', 8 | stream: ringbuffer, 9 | level: 'debug' 10 | } ] 11 | }); 12 | 13 | log.info('hello world'); 14 | console.log(ringbuffer.records); 15 | -------------------------------------------------------------------------------- /examples/rot-specific-levels.js: -------------------------------------------------------------------------------- 1 | var bunyan = require('./'), 2 | safeCycles = bunyan.safeCycles; 3 | var util = require('util'); 4 | 5 | 6 | function SpecificLevelStream(levels, stream) { 7 | var self = this; 8 | this.levels = {}; 9 | levels.forEach(function (lvl) { 10 | self.levels[bunyan.resolveLevel(lvl)] = true; 11 | }); 12 | this.stream = stream; 13 | } 14 | SpecificLevelStream.prototype.write = function (rec) { 15 | if (this.levels[rec.level] !== undefined) { 16 | var str = JSON.stringify(rec, safeCycles()) + '\n'; 17 | this.stream.write(str); 18 | } 19 | } 20 | 21 | 22 | var log = bunyan.createLogger({ 23 | name: 'rot-specific-levels', 24 | streams: [ 25 | { 26 | type: 'raw', 27 | level: 'debug', 28 | stream: new SpecificLevelStream( 29 | ['debug'], 30 | new bunyan.RotatingFileStream({ 31 | path: './rot-specific-levels.debug.log', 32 | period: '3000ms', 33 | count: 10 34 | }) 35 | ) 36 | }, 37 | { 38 | type: 'raw', 39 | level: 'info', 40 | stream: new SpecificLevelStream( 41 | ['info'], 42 | new bunyan.RotatingFileStream({ 43 | path: './rot-specific-levels.info.log', 44 | period: '3000ms', 45 | count: 10 46 | }) 47 | ) 48 | } 49 | ] 50 | }); 51 | 52 | 53 | setInterval(function () { 54 | log.trace('hi on trace') // goes nowhere 55 | log.debug('hi on debug') // goes to rot-specific-levels.debug.log.* 56 | log.info('hi on info') // goes to rot-specific-levels.info.log.* 57 | }, 1000); 58 | -------------------------------------------------------------------------------- /examples/server.js: -------------------------------------------------------------------------------- 1 | // Example logging HTTP server request and response objects. 2 | 3 | var http = require('http'); 4 | var bunyan = require('../lib/bunyan'); 5 | 6 | var log = bunyan.createLogger({ 7 | name: 'myserver', 8 | serializers: { 9 | req: bunyan.stdSerializers.req, 10 | res: bunyan.stdSerializers.res 11 | } 12 | }); 13 | 14 | var server = http.createServer(function (req, res) { 15 | log.info({req: req}, 'start request'); // <-- this is the guy we're testing 16 | res.writeHead(200, {'Content-Type': 'text/plain'}); 17 | res.end('Hello World\n'); 18 | log.info({res: res}, 'done response'); // <-- this is the guy we're testing 19 | }); 20 | server.listen(1337, '127.0.0.1', function () { 21 | log.info('server listening'); 22 | var options = { 23 | port: 1337, 24 | hostname: '127.0.0.1', 25 | path: '/path?q=1#anchor', 26 | headers: { 27 | 'X-Hi': 'Mom' 28 | } 29 | }; 30 | var req = http.request(options); 31 | req.on('response', function (res) { 32 | res.on('end', function () { 33 | process.exit(); 34 | }) 35 | }); 36 | req.write('hi from the client'); 37 | req.end(); 38 | }); 39 | 40 | 41 | /* BEGIN JSSTYLED */ 42 | /** 43 | * 44 | * $ node server.js 45 | * {"service":"myserver","hostname":"banana.local","level":3,"msg":"server listening","time":"2012-02-02T05:32:13.257Z","v":0} 46 | * {"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} 47 | * {"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} 48 | * 49 | * $ node server.js | ../bin/bunyan 50 | * [2012-02-02T05:32:16.006Z] INFO: myserver on banana.local: server listening 51 | * [2012-02-02T05:32:16.010Z] INFO: myserver on banana.local: start request 52 | * GET /path?q=1#anchor 53 | * x-hi: Mom 54 | * connection: close 55 | * [2012-02-02T05:32:16.011Z] INFO: myserver on banana.local: done response 56 | * HTTP/1.1 200 OK 57 | * Content-Type: text/plain 58 | * Connection: close 59 | * Transfer-Encoding: chunked 60 | * (body) 61 | * 62 | */ 63 | /* END JSSTYLED */ 64 | -------------------------------------------------------------------------------- /examples/specific-level-streams.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | * was a request to have 4 | * bunyan core support logging some levels to one stream and others to another, 5 | * *not* limited by bunyan's current support that a stream `level` implies 6 | * "that level and higher". 7 | * 8 | * Let's do that with a custom raw stream. 9 | */ 10 | 11 | var bunyan = require('../lib/bunyan'), 12 | safeCycles = bunyan.safeCycles; 13 | var fs = require('fs'); 14 | 15 | /** 16 | * Use case #1: cli tool that outputs errors on stderr and everything else on 17 | * stdout. 18 | * 19 | * First make a raw bunyan stream (i.e. an object with a `.write(rec)`). 20 | */ 21 | function SpecificLevelStream(levels, stream) { 22 | var self = this; 23 | this.levels = {}; 24 | levels.forEach(function (lvl) { 25 | self.levels[bunyan.resolveLevel(lvl)] = true; 26 | }); 27 | this.stream = stream; 28 | } 29 | SpecificLevelStream.prototype.write = function (rec) { 30 | if (this.levels[rec.level] !== undefined) { 31 | var str = JSON.stringify(rec, safeCycles()) + '\n'; 32 | this.stream.write(str); 33 | } 34 | } 35 | 36 | var log1 = bunyan.createLogger({ 37 | name: 'use-case-1', 38 | streams: [ { 39 | level: 'trace', 40 | type: 'raw', 41 | stream: new SpecificLevelStream( 42 | ['trace', 'debug', 'info', 'warn'], 43 | process.stdout) 44 | }, { 45 | level: 'error', 46 | type: 'raw', 47 | stream: new SpecificLevelStream( 48 | ['error'], 49 | process.stderr) 50 | } ] 51 | }); 52 | 53 | log1.info('hi at info level (this should be on stdout)'); 54 | log1.error('alert alert (this should be on stderr)'); 55 | 56 | 57 | /** 58 | * Use case #2: nginx-style logger that separates error- and access-logs 59 | */ 60 | var log2 = bunyan.createLogger({ 61 | name: 'use-case-2', 62 | streams: [ { 63 | level: 'info', 64 | type: 'raw', 65 | stream: new SpecificLevelStream( 66 | ['info'], 67 | fs.createWriteStream('specific-level-streams-http.log', 68 | {flags: 'a', encoding: 'utf8'})) 69 | }, { 70 | level: 'warn', 71 | path: 'specific-level-streams-http.err.log' 72 | } ] 73 | }); 74 | 75 | log2.info('200 GET /blah'); 76 | log2.error('500 GET /boom'); 77 | -------------------------------------------------------------------------------- /examples/src.js: -------------------------------------------------------------------------------- 1 | // Show the usage of `src: true` config option to get log call source info in 2 | // log records (the `src` field). 3 | 4 | var bunyan = require('../lib/bunyan'); 5 | 6 | var log = bunyan.createLogger({name: 'src-example', src: true}); 7 | 8 | log.info('one'); 9 | log.info('two'); 10 | function doSomeFoo() { 11 | log.info({foo:'bar'}, 'three'); 12 | } 13 | doSomeFoo(); 14 | 15 | function Wuzzle(options) { 16 | this.log = options.log; 17 | this.log.info('creating a wuzzle') 18 | } 19 | Wuzzle.prototype.woos = function () { 20 | this.log.warn('This wuzzle is woosey.') 21 | } 22 | 23 | var wuzzle = new Wuzzle({log: log.child({component: 'wuzzle'})}); 24 | wuzzle.woos(); 25 | log.info('done with the wuzzle') 26 | -------------------------------------------------------------------------------- /examples/unstringifyable.js: -------------------------------------------------------------------------------- 1 | // See how bunyan behaves with an un-stringify-able object. 2 | var bunyan = require('../lib/bunyan'); 3 | 4 | var log = bunyan.createLogger({src: true, name: 'foo'}); 5 | 6 | // Make a circular object (cannot be JSON-ified). 7 | var myobj = { 8 | foo: 'bar' 9 | }; 10 | myobj.myobj = myobj; 11 | 12 | log.info({obj: myobj}, 'hi there'); // <--- here 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bunyan", 3 | "version": "2.0.5", 4 | "description": "a JSON logging library for node.js services", 5 | "author": "Trent Mick (http://trentm.com)", 6 | "main": "./lib/bunyan.js", 7 | "bin": { 8 | "bunyan": "./bin/bunyan" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/trentm/node-bunyan.git" 13 | }, 14 | "engines": [ 15 | "node >=0.10.0" 16 | ], 17 | "keywords": [ 18 | "log", 19 | "logging", 20 | "log4j", 21 | "json", 22 | "bunyan" 23 | ], 24 | "license": "MIT", 25 | "files": [ 26 | "bin", 27 | "lib" 28 | ], 29 | "dependencies": { 30 | "exeunt": "1.1.0" 31 | }, 32 | "// dtrace-provider": "required for dtrace features", 33 | "// mv": "required for RotatingFileStream", 34 | "// moment": "required for local time with CLI", 35 | "optionalDependencies": { 36 | "dtrace-provider": "~0.8", 37 | "mv": "~2", 38 | "safe-json-stringify": "~1", 39 | "moment": "^2.19.3" 40 | }, 41 | "devDependencies": { 42 | "ben": "0.0.0", 43 | "markdown-toc": "0.12.x", 44 | "tap": "^9.0.3", 45 | "vasync": "1.4.3", 46 | "verror": "1.3.3" 47 | }, 48 | "scripts": { 49 | "check": "make check", 50 | "test": "tap test/*.test.js # skip dtrace tests" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test/add-stream.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Trent Mick. All rights reserved. 3 | * 4 | * Test stream adding. 5 | */ 6 | 7 | var test = require('tap').test; 8 | 9 | var bunyan = require('../lib/bunyan'); 10 | 11 | 12 | test('non-writables passed as stream', function (t) { 13 | var things = ['process.stdout', {}]; 14 | things.forEach(function (thing) { 15 | function createLogger() { 16 | bunyan.createLogger({ 17 | name: 'foo', 18 | stream: thing 19 | }); 20 | } 21 | t.throws(createLogger, 22 | /stream is not writable/, 23 | '"stream" stream is not writable'); 24 | }) 25 | t.end(); 26 | }); 27 | 28 | test('proper stream', function (t) { 29 | var log = bunyan.createLogger({ 30 | name: 'foo', 31 | stream: process.stdout 32 | }); 33 | t.ok('should not throw'); 34 | t.end(); 35 | }); 36 | -------------------------------------------------------------------------------- /test/buffer.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Trent Mick 3 | * Copyright 2012 Joyent Inc. 4 | * 5 | * Test logging with (accidental) usage of buffers. 6 | */ 7 | 8 | var util = require('util'), 9 | inspect = util.inspect, 10 | format = util.format; 11 | var test = require('tap').test; 12 | 13 | var bunyan = require('../lib/bunyan'); 14 | 15 | 16 | function Catcher() { 17 | this.records = []; 18 | } 19 | Catcher.prototype.write = function (record) { 20 | this.records.push(record); 21 | } 22 | 23 | var catcher = new Catcher(); 24 | var log = new bunyan.createLogger({ 25 | name: 'buffer.test', 26 | streams: [ 27 | { 28 | type: 'raw', 29 | stream: catcher, 30 | level: 'trace' 31 | } 32 | ] 33 | }); 34 | 35 | 36 | test('log.info(BUFFER)', function (t) { 37 | var b = Buffer.from ? Buffer.from('foo') : new Buffer('foo'); 38 | 39 | ['trace', 40 | 'debug', 41 | 'info', 42 | 'warn', 43 | 'error', 44 | 'fatal'].forEach(function (lvl) { 45 | log[lvl].call(log, b); 46 | var rec = catcher.records[catcher.records.length - 1]; 47 | t.equal(rec.msg, inspect(b), 48 | format('log.%s msg is inspect(BUFFER)', lvl)); 49 | t.ok(rec['0'] === undefined, 50 | 'no "0" array index key in record: ' + inspect(rec['0'])); 51 | t.ok(rec['parent'] === undefined, 52 | 'no "parent" array index key in record: ' + inspect(rec['parent'])); 53 | 54 | log[lvl].call(log, b, 'bar'); 55 | var rec = catcher.records[catcher.records.length - 1]; 56 | t.equal(rec.msg, inspect(b) + ' bar', format( 57 | 'log.%s(BUFFER, "bar") msg is inspect(BUFFER) + " bar"', lvl)); 58 | }); 59 | 60 | t.end(); 61 | }); 62 | 63 | 64 | //test('log.info({buf: BUFFER})', function (t) { 65 | // var b = Buffer.from ? Buffer.from('foo') : new Buffer('foo'); 66 | // 67 | // // Really there isn't much Bunyan can do here. See 68 | // // . An unwelcome hack would 69 | // // be to monkey-patch in Buffer.toJSON. Bletch. 70 | // log.info({buf: b}, 'my message'); 71 | // var rec = catcher.records[catcher.records.length - 1]; 72 | // 73 | // t.end(); 74 | //}); 75 | -------------------------------------------------------------------------------- /test/child-behaviour.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Trent Mick 3 | * 4 | * Test some `.child(...)` behaviour. 5 | */ 6 | 7 | var test = require('tap').test; 8 | 9 | var bunyan = require('../lib/bunyan'); 10 | 11 | 12 | function CapturingStream(recs) { 13 | this.recs = recs || []; 14 | } 15 | CapturingStream.prototype.write = function (rec) { 16 | this.recs.push(rec); 17 | } 18 | 19 | 20 | 21 | test('child can add stream', function (t) { 22 | var dadStream = new CapturingStream(); 23 | var dad = bunyan.createLogger({ 24 | name: 'surname', 25 | streams: [ { 26 | type: 'raw', 27 | stream: dadStream, 28 | level: 'info' 29 | } ] 30 | }); 31 | 32 | var sonStream = new CapturingStream(); 33 | var son = dad.child({ 34 | component: 'son', 35 | streams: [ { 36 | type: 'raw', 37 | stream: sonStream, 38 | level: 'debug' 39 | } ] 40 | }); 41 | 42 | dad.info('info from dad'); 43 | dad.debug('debug from dad'); 44 | son.debug('debug from son'); 45 | 46 | var rec; 47 | t.equal(dadStream.recs.length, 1); 48 | rec = dadStream.recs[0]; 49 | t.equal(rec.msg, 'info from dad'); 50 | t.equal(sonStream.recs.length, 1); 51 | rec = sonStream.recs[0]; 52 | t.equal(rec.msg, 'debug from son'); 53 | 54 | t.end(); 55 | }); 56 | 57 | 58 | test('child can set level of inherited streams', function (t) { 59 | var dadStream = new CapturingStream(); 60 | var dad = bunyan.createLogger({ 61 | name: 'surname', 62 | streams: [ { 63 | type: 'raw', 64 | stream: dadStream, 65 | level: 'info' 66 | } ] 67 | }); 68 | 69 | // Intention here is that the inherited `dadStream` logs at 'debug' level 70 | // for the son. 71 | var son = dad.child({ 72 | component: 'son', 73 | level: 'debug' 74 | }); 75 | 76 | dad.info('info from dad'); 77 | dad.debug('debug from dad'); 78 | son.debug('debug from son'); 79 | 80 | var rec; 81 | t.equal(dadStream.recs.length, 2); 82 | rec = dadStream.recs[0]; 83 | t.equal(rec.msg, 'info from dad'); 84 | rec = dadStream.recs[1]; 85 | t.equal(rec.msg, 'debug from son'); 86 | 87 | t.end(); 88 | }); 89 | 90 | 91 | test('child can set level of inherited streams and add streams', function (t) { 92 | var dadStream = new CapturingStream(); 93 | var dad = bunyan.createLogger({ 94 | name: 'surname', 95 | streams: [ { 96 | type: 'raw', 97 | stream: dadStream, 98 | level: 'info' 99 | } ] 100 | }); 101 | 102 | // Intention here is that the inherited `dadStream` logs at 'debug' level 103 | // for the son. 104 | var sonStream = new CapturingStream(); 105 | var son = dad.child({ 106 | component: 'son', 107 | level: 'trace', 108 | streams: [ { 109 | type: 'raw', 110 | stream: sonStream, 111 | level: 'debug' 112 | } ] 113 | }); 114 | 115 | dad.info('info from dad'); 116 | dad.trace('trace from dad'); 117 | son.trace('trace from son'); 118 | son.debug('debug from son'); 119 | 120 | t.equal(dadStream.recs.length, 3); 121 | t.equal(dadStream.recs[0].msg, 'info from dad'); 122 | t.equal(dadStream.recs[1].msg, 'trace from son'); 123 | t.equal(dadStream.recs[2].msg, 'debug from son'); 124 | 125 | t.equal(sonStream.recs.length, 1); 126 | t.equal(sonStream.recs[0].msg, 'debug from son'); 127 | 128 | t.end(); 129 | }); 130 | 131 | // issue #291 132 | test('child should not lose parent "hostname"', function (t) { 133 | var stream = new CapturingStream(); 134 | var dad = bunyan.createLogger({ 135 | name: 'hostname-test', 136 | hostname: 'bar0', 137 | streams: [ { 138 | type: 'raw', 139 | stream: stream, 140 | level: 'info' 141 | } ] 142 | }); 143 | var son = dad.child({component: 'son'}); 144 | 145 | dad.info('HI'); 146 | son.info('hi'); 147 | 148 | t.equal(stream.recs.length, 2); 149 | t.equal(stream.recs[0].hostname, 'bar0'); 150 | t.equal(stream.recs[1].hostname, 'bar0'); 151 | t.equal(stream.recs[1].component, 'son'); 152 | 153 | t.end(); 154 | }); 155 | -------------------------------------------------------------------------------- /test/cli-client-req.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Trent Mick 3 | * 4 | * Test the bunyan CLI's handling of the "client_req" field. 5 | * "client_req" is a common-ish Bunyan log field from restify-clients. See: 6 | * // JSSTYLED 7 | * https://github.com/restify/clients/blob/85374f87db9f4469de2605b6b15632b317cc12be/lib/helpers/bunyan.js#L213 8 | */ 9 | 10 | var exec = require('child_process').exec; 11 | var fs = require('fs'); 12 | var os = require('os'); 13 | var path = require('path'); 14 | var _ = require('util').format; 15 | var test = require('tap').test; 16 | 17 | 18 | // ---- globals 19 | 20 | var BUNYAN = path.resolve(__dirname, '../bin/bunyan'); 21 | if (os.platform() === 'win32') { 22 | BUNYAN = process.execPath + ' ' + BUNYAN; 23 | } 24 | 25 | 26 | // ---- tests 27 | 28 | test('client_req extra newlines, client_res={} (pull #252)', function (t) { 29 | var expect = [ 30 | /* BEGIN JSSTYLED */ 31 | '[2016-02-10T07:28:40.510Z] TRACE: aclientreq/23280 on danger0.local: request sent', 32 | ' GET /--ping HTTP/1.1', 33 | '[2016-02-10T07:28:41.419Z] TRACE: aclientreq/23280 on danger0.local: Response received', 34 | ' HTTP/1.1 200 OK', 35 | ' request-id: e8a5a700-cfc7-11e5-a3dc-3b85d20f26ef', 36 | ' content-type: application/json' 37 | /* END JSSTYLED */ 38 | ].join('\n') + '\n'; 39 | exec(_('%s %s/corpus/clientreqres.log', BUNYAN, __dirname), 40 | function (err, stdout, stderr) { 41 | t.ifError(err); 42 | t.equal(stdout, expect); 43 | t.end(); 44 | }); 45 | }); 46 | 47 | 48 | test('client_req.address is not used for Host header in 2.x (issue #504)', 49 | function (t) { 50 | exec(_('%s %s/corpus/client-req-with-address.log', BUNYAN, __dirname), 51 | function (err, stdout, stderr) { 52 | t.ifError(err) 53 | t.equal(stdout, [ 54 | // JSSTYLED 55 | '[2017-05-12T23:59:15.877Z] TRACE: minfo/66266 on sharptooth.local: request sent (client_req.address=127.0.0.1)', 56 | ' HEAD /dap/stor HTTP/1.1', 57 | ' accept: application/json, */*', 58 | ' host: foo.example.com', 59 | ' date: Fri, 12 May 2017 23:59:15 GMT', 60 | '' 61 | ].join('\n')); 62 | t.end(); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /test/cli-res.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Trent Mick 3 | * 4 | * Test the bunyan CLI's handling of the "res" field. 5 | */ 6 | 7 | var exec = require('child_process').exec; 8 | var fs = require('fs'); 9 | var os = require('os'); 10 | var path = require('path'); 11 | var _ = require('util').format; 12 | var test = require('tap').test; 13 | 14 | 15 | // ---- globals 16 | 17 | var BUNYAN = path.resolve(__dirname, '../bin/bunyan'); 18 | if (os.platform() === 'win32') { 19 | BUNYAN = process.execPath + ' ' + BUNYAN; 20 | } 21 | 22 | 23 | // ---- tests 24 | 25 | test('res with "header" string (issue #444)', function (t) { 26 | var expect = [ 27 | /* BEGIN JSSTYLED */ 28 | '[2017-08-02T22:37:34.798Z] INFO: res-header/76488 on danger0.local: response sent', 29 | ' HTTP/1.1 200 OK', 30 | ' Foo: bar', 31 | ' Date: Wed, 02 Aug 2017 22:37:34 GMT', 32 | ' Connection: keep-alive', 33 | ' Content-Length: 21' 34 | /* END JSSTYLED */ 35 | ].join('\n') + '\n'; 36 | exec(_('%s %s/corpus/res-header.log', BUNYAN, __dirname), 37 | function (err, stdout, stderr) { 38 | t.ifError(err); 39 | t.equal(stdout, expect); 40 | t.end(); 41 | }); 42 | }); 43 | 44 | test('res without "header"', function (t) { 45 | var expect = [ 46 | /* BEGIN JSSTYLED */ 47 | '[2017-08-02T22:37:34.798Z] INFO: res-header/76488 on danger0.local: response sent', 48 | ' HTTP/1.1 200 OK' 49 | /* END JSSTYLED */ 50 | ].join('\n') + '\n'; 51 | exec(_('%s %s/corpus/res-without-header.log', BUNYAN, __dirname), 52 | function (err, stdout, stderr) { 53 | t.ifError(err); 54 | t.equal(stdout, expect); 55 | t.end(); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/cli.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Trent Mick 3 | * 4 | * Test the `bunyan` CLI. 5 | */ 6 | 7 | var p = console.warn; 8 | var exec = require('child_process').exec; 9 | var fs = require('fs'); 10 | var os = require('os'); 11 | var path = require('path'); 12 | var _ = require('util').format; 13 | var test = require('tap').test; 14 | var vasync = require('vasync'); 15 | 16 | 17 | // ---- globals 18 | 19 | var BUNYAN = path.resolve(__dirname, '../bin/bunyan'); 20 | if (os.platform() === 'win32') { 21 | BUNYAN = process.execPath + ' ' + BUNYAN; 22 | } 23 | 24 | 25 | // ---- support stuff 26 | 27 | /** 28 | * Copies over all keys in `from` to `to`, or 29 | * to a new object if `to` is not given. 30 | */ 31 | function objCopy(from, to) { 32 | if (to === undefined) { 33 | to = {}; 34 | } 35 | for (var k in from) { 36 | to[k] = from[k]; 37 | } 38 | return to; 39 | } 40 | 41 | 42 | // ---- tests 43 | 44 | test('--version', function (t) { 45 | var version = require('../package.json').version; 46 | exec(BUNYAN + ' --version', function (err, stdout, stderr) { 47 | t.ifError(err) 48 | t.equal(stdout, 'bunyan ' + version + '\n'); 49 | t.end(); 50 | }); 51 | }); 52 | 53 | test('--help', function (t) { 54 | exec(BUNYAN + ' --help', function (err, stdout, stderr) { 55 | t.ifError(err) 56 | t.ok(stdout.indexOf('General options:') !== -1); 57 | t.end(); 58 | }); 59 | }); 60 | 61 | test('-h', function (t) { 62 | exec(BUNYAN + ' -h', function (err, stdout, stderr) { 63 | t.ifError(err) 64 | t.ok(stdout.indexOf('General options:') !== -1); 65 | t.end(); 66 | }); 67 | }); 68 | 69 | test('--bogus', function (t) { 70 | exec(BUNYAN + ' --bogus', function (err, stdout, stderr) { 71 | t.ok(err, 'should error out') 72 | t.equal(err.code, 1, '... with exit code 1') 73 | t.end(); 74 | }); 75 | }); 76 | 77 | test('simple.log', function (t) { 78 | exec(_('%s %s/corpus/simple.log', BUNYAN, __dirname), 79 | function (err, stdout, stderr) { 80 | t.ifError(err) 81 | t.equal(stdout, 82 | '[2012-02-08T22:56:52.856Z] INFO: myservice/123 on example.com: ' 83 | + 'My message\n'); 84 | t.end(); 85 | }); 86 | }); 87 | test('cat simple.log', function (t) { 88 | exec(_('cat %s/corpus/simple.log | %s', __dirname, BUNYAN), 89 | function (err, stdout, stderr) { 90 | t.ifError(err) 91 | t.equal(stdout, 92 | /* JSSTYLED */ 93 | '[2012-02-08T22:56:52.856Z] INFO: myservice/123 on example.com: My message\n'); 94 | t.end(); 95 | } 96 | ); 97 | }); 98 | 99 | // Test some local/UTC time handling by changing to a known non-UTC timezone 100 | // for some tests. 101 | // 102 | // I don't know how to effectively do this on Windows (at least 103 | // https://stackoverflow.com/questions/2611017 concurs), so we skip these on 104 | // Windows. Help is welcome if you know how to do this on Windows. 105 | test('time TZ tests', { 106 | skip: os.platform() === 'win32' 107 | ? 'do not know how to set timezone on Windows' : false 108 | }, function (suite) { 109 | // A stable 'TZ' for 'local' timezone output. 110 | tzEnv = objCopy(process.env); 111 | tzEnv.TZ = 'Pacific/Honolulu'; 112 | 113 | test('time: simple.log local long', function (t) { 114 | exec(_('%s -o long -L %s/corpus/simple.log', BUNYAN, __dirname), 115 | {env: tzEnv}, function (err, stdout, stderr) { 116 | t.ifError(err) 117 | t.equal(stdout, 118 | // JSSTYLED 119 | '[2012-02-08T12:56:52.856-10:00] INFO: myservice/123 on example.com: ' 120 | + 'My message\n'); 121 | t.end(); 122 | }); 123 | }); 124 | test('time: simple.log utc long', function (t) { 125 | exec(_('%s -o long --time utc %s/corpus/simple.log', BUNYAN, __dirname), 126 | {env: tzEnv}, function (err, stdout, stderr) { 127 | t.ifError(err) 128 | t.equal(stdout, 129 | // JSSTYLED 130 | '[2012-02-08T22:56:52.856Z] INFO: myservice/123 on example.com: ' 131 | + 'My message\n'); 132 | t.end(); 133 | }); 134 | }); 135 | test('time: simple.log local short', function (t) { 136 | exec(_('%s -o short -L %s/corpus/simple.log', BUNYAN, __dirname), 137 | {env: tzEnv}, function (err, stdout, stderr) { 138 | t.ifError(err) 139 | t.equal(stdout, 140 | '12:56:52.856 INFO myservice: ' 141 | + 'My message\n'); 142 | t.end(); 143 | }); 144 | }); 145 | test('time: simple.log utc short', function (t) { 146 | exec(_('%s -o short %s/corpus/simple.log', BUNYAN, __dirname), 147 | {env: tzEnv}, function (err, stdout, stderr) { 148 | t.ifError(err) 149 | t.equal(stdout, 150 | '22:56:52.856Z INFO myservice: ' 151 | + 'My message\n'); 152 | t.end(); 153 | }); 154 | }); 155 | 156 | suite.end(); 157 | }); 158 | 159 | test('simple.log with color', function (t) { 160 | exec(_('%s --color %s/corpus/simple.log', BUNYAN, __dirname), 161 | function (err, stdout, stderr) { 162 | t.ifError(err) 163 | t.equal(stdout, 164 | /* JSSTYLED */ 165 | '[2012-02-08T22:56:52.856Z] \u001b[36m INFO\u001b[39m: myservice/123 on example.com: \u001b[36mMy message\u001b[39m\n\u001b[0m'); 166 | t.end(); 167 | }); 168 | }); 169 | 170 | test('extrafield.log', function (t) { 171 | exec(_('%s %s/corpus/extrafield.log', BUNYAN, __dirname), 172 | function (err, stdout, stderr) { 173 | t.ifError(err) 174 | t.equal(stdout, 175 | '[2012-02-08T22:56:52.856Z] INFO: myservice/123 on example.com: ' 176 | + 'My message (extra=field)\n'); 177 | t.end(); 178 | }); 179 | }); 180 | test('extrafield.log with color', function (t) { 181 | exec(_('%s --color %s/corpus/extrafield.log', BUNYAN, __dirname), 182 | function (err, stdout, stderr) { 183 | t.ifError(err) 184 | t.equal(stdout, 185 | '[2012-02-08T22:56:52.856Z] \u001b[36m INFO\u001b[39m: ' 186 | + 'myservice/123 ' 187 | + 'on example.com: \u001b[36mMy message\u001b[39m' 188 | + ' (extra=field)\n\u001b[0m'); 189 | t.end(); 190 | }); 191 | }); 192 | 193 | test('bogus.log', function (t) { 194 | exec(_('%s %s/corpus/bogus.log', BUNYAN, __dirname), 195 | function (err, stdout, stderr) { 196 | t.ifError(err) 197 | t.equal(stdout, 'not a JSON line\n{"hi": "there"}\n'); 198 | t.end(); 199 | }); 200 | }); 201 | 202 | test('bogus.log -j', function (t) { 203 | exec(_('%s -j %s/corpus/bogus.log', BUNYAN, __dirname), 204 | function (err, stdout, stderr) { 205 | t.ifError(err) 206 | t.equal(stdout, 'not a JSON line\n{"hi": "there"}\n'); 207 | t.end(); 208 | }); 209 | }); 210 | 211 | test('all.log', function (t) { 212 | exec(_('%s %s/corpus/all.log', BUNYAN, __dirname), 213 | function (err, stdout, stderr) { 214 | // Just make sure don't blow up on this. 215 | t.ifError(err) 216 | t.end(); 217 | }); 218 | }); 219 | 220 | test('simple.log doesnotexist1.log doesnotexist2.log', function (t) { 221 | exec(_('%s %s/corpus/simple.log doesnotexist1.log doesnotexist2.log', 222 | BUNYAN, __dirname), 223 | function (err, stdout, stderr) { 224 | t.ok(err) 225 | t.equal(err.code, 2) 226 | t.equal(stdout, 227 | /* JSSTYLED */ 228 | '[2012-02-08T22:56:52.856Z] INFO: myservice/123 on example.com: My message\n'); 229 | // Note: node v0.6.10: 230 | // ENOENT, no such file or directory 'asdf.log' 231 | // but node v0.6.14: 232 | // ENOENT, open 'asdf.log' 233 | // io.js 2.2 (at least): 234 | // ENOENT: no such file or directory, open 'doesnotexist1.log' 235 | // in GitHub Actions windows-latest runner: 236 | // JSSTYLED 237 | // ENOENT: no such file or directory, open 'D:\\a\\node-bunyan\\node-bunyan\\doesnotexist1.log 238 | var matches = [ 239 | /^bunyan: ENOENT.*?, open '.*?doesnotexist1.log'/m, 240 | /^bunyan: ENOENT.*?, open '.*?doesnotexist2.log'/m, 241 | ]; 242 | matches.forEach(function (match) { 243 | t.ok(match.test(stderr), 'stderr matches ' + match.toString() + 244 | ', stderr=' + JSON .stringify(stderr)); 245 | }); 246 | t.end(); 247 | } 248 | ); 249 | }); 250 | 251 | test('multiple logs', function (t) { 252 | var cmd = _('%s %s/corpus/log1.log %s/corpus/log2.log', 253 | BUNYAN, __dirname, __dirname); 254 | exec(cmd, function (err, stdout, stderr) { 255 | t.ifError(err); 256 | t.equal(stdout, [ 257 | /* BEGIN JSSTYLED */ 258 | '[2012-05-08T16:57:55.586Z] INFO: agent1/73267 on headnode: message\n', 259 | '[2012-05-08T16:58:55.586Z] INFO: agent2/73267 on headnode: message\n', 260 | '[2012-05-08T17:01:49.339Z] INFO: agent2/73267 on headnode: message\n', 261 | '[2012-05-08T17:02:47.404Z] INFO: agent2/73267 on headnode: message\n', 262 | '[2012-05-08T17:02:49.339Z] INFO: agent1/73267 on headnode: message\n', 263 | '[2012-05-08T17:02:49.404Z] INFO: agent1/73267 on headnode: message\n', 264 | '[2012-05-08T17:02:49.404Z] INFO: agent1/73267 on headnode: message\n', 265 | '[2012-05-08T17:02:57.404Z] INFO: agent2/73267 on headnode: message\n', 266 | '[2012-05-08T17:08:01.105Z] INFO: agent2/76156 on headnode: message\n', 267 | /* END JSSTYLED */ 268 | ].join('')); 269 | t.end(); 270 | }); 271 | }); 272 | 273 | test('multiple logs, bunyan format', function (t) { 274 | var cmd = _('%s -o bunyan %s/corpus/log1.log %s/corpus/log2.log', 275 | BUNYAN, __dirname, __dirname); 276 | exec(cmd, function (err, stdout, stderr) { 277 | t.ifError(err); 278 | t.equal(stdout, [ 279 | /* BEGIN JSSTYLED */ 280 | '{"name":"agent1","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T16:57:55.586Z","v":0}', 281 | '{"name":"agent2","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T16:58:55.586Z","v":0}', 282 | '{"name":"agent2","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T17:01:49.339Z","v":0}', 283 | '{"name":"agent2","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T17:02:47.404Z","v":0}', 284 | '{"name":"agent1","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T17:02:49.339Z","v":0}', 285 | '{"name":"agent1","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T17:02:49.404Z","v":0}', 286 | '{"name":"agent1","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T17:02:49.404Z","v":0}', 287 | '{"name":"agent2","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T17:02:57.404Z","v":0}', 288 | '{"name":"agent2","pid":76156,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T17:08:01.105Z","v":0}', 289 | '' 290 | /* END JSSTYLED */ 291 | ].join('\n')); 292 | t.end(); 293 | }); 294 | }); 295 | 296 | test('log1.log.gz', function (t) { 297 | exec(_('%s %s/corpus/log1.log.gz', BUNYAN, __dirname), 298 | function (err, stdout, stderr) { 299 | t.ifError(err); 300 | t.equal(stdout, [ 301 | /* BEGIN JSSTYLED */ 302 | '[2012-05-08T16:57:55.586Z] INFO: agent1/73267 on headnode: message\n', 303 | '[2012-05-08T17:02:49.339Z] INFO: agent1/73267 on headnode: message\n', 304 | '[2012-05-08T17:02:49.404Z] INFO: agent1/73267 on headnode: message\n', 305 | '[2012-05-08T17:02:49.404Z] INFO: agent1/73267 on headnode: message\n', 306 | /* END JSSTYLED */ 307 | ].join('')); 308 | t.end(); 309 | }); 310 | }); 311 | 312 | test('mixed text and gzip logs', function (t) { 313 | var cmd = _('%s %s/corpus/log1.log.gz %s/corpus/log2.log', 314 | BUNYAN, __dirname, __dirname); 315 | exec(cmd, function (err, stdout, stderr) { 316 | t.ifError(err); 317 | t.equal(stdout, [ 318 | /* BEGIN JSSTYLED */ 319 | '[2012-05-08T16:57:55.586Z] INFO: agent1/73267 on headnode: message\n', 320 | '[2012-05-08T16:58:55.586Z] INFO: agent2/73267 on headnode: message\n', 321 | '[2012-05-08T17:01:49.339Z] INFO: agent2/73267 on headnode: message\n', 322 | '[2012-05-08T17:02:47.404Z] INFO: agent2/73267 on headnode: message\n', 323 | '[2012-05-08T17:02:49.339Z] INFO: agent1/73267 on headnode: message\n', 324 | '[2012-05-08T17:02:49.404Z] INFO: agent1/73267 on headnode: message\n', 325 | '[2012-05-08T17:02:49.404Z] INFO: agent1/73267 on headnode: message\n', 326 | '[2012-05-08T17:02:57.404Z] INFO: agent2/73267 on headnode: message\n', 327 | '[2012-05-08T17:08:01.105Z] INFO: agent2/76156 on headnode: message\n', 328 | /* END JSSTYLED */ 329 | ].join('')); 330 | t.end(); 331 | }); 332 | }); 333 | 334 | test('--level 40', function (t) { 335 | expect = [ 336 | /* BEGIN JSSTYLED */ 337 | '# levels\n', 338 | '[2012-02-08T22:56:53.856Z] WARN: myservice/123 on example.com: My message\n', 339 | '[2012-02-08T22:56:54.856Z] ERROR: myservice/123 on example.com: My message\n', 340 | '[2012-02-08T22:56:55.856Z] LVL55: myservice/123 on example.com: My message\n', 341 | '[2012-02-08T22:56:56.856Z] FATAL: myservice/123 on example.com: My message\n', 342 | '\n', 343 | '# extra fields\n', 344 | '\n', 345 | '# bogus\n', 346 | 'not a JSON line\n', 347 | '{"hi": "there"}\n' 348 | /* END JSSTYLED */ 349 | ].join(''); 350 | exec(_('%s -l 40 %s/corpus/all.log', BUNYAN, __dirname), 351 | function (err, stdout, stderr) { 352 | t.ifError(err); 353 | t.equal(stdout, expect); 354 | exec(_('%s --level 40 %s/corpus/all.log', BUNYAN, __dirname), 355 | function (err, stdout, stderr) { 356 | t.ifError(err); 357 | t.equal(stdout, expect); 358 | t.end(); 359 | }); 360 | }); 361 | }); 362 | 363 | test('--condition "this.level === 10 && this.pid === 123"', function (t) { 364 | var expect = [ 365 | '# levels\n', 366 | /* JSSTYLED */ 367 | '[2012-02-08T22:56:50.856Z] TRACE: myservice/123 on example.com: My message\n', 368 | '\n', 369 | '# extra fields\n', 370 | '\n', 371 | '# bogus\n', 372 | 'not a JSON line\n', 373 | '{"hi": "there"}\n' 374 | ].join(''); 375 | var cmd = _('%s -c "this.level === 10 && this.pid === 123"' 376 | + ' %s/corpus/all.log', BUNYAN, __dirname); 377 | exec(cmd, function (err, stdout, stderr) { 378 | t.ifError(err); 379 | t.equal(stdout, expect); 380 | var cmd = _( 381 | '%s --condition "this.level === 10 && this.pid === 123"' 382 | + ' %s/corpus/all.log', BUNYAN, __dirname); 383 | exec(cmd, function (err, stdout, stderr) { 384 | t.ifError(err); 385 | t.equal(stdout, expect); 386 | t.end(); 387 | }); 388 | }); 389 | }); 390 | 391 | test('--condition "this.level === TRACE', function (t) { 392 | var expect = [ 393 | '# levels\n', 394 | /* JSSTYLED */ 395 | '[2012-02-08T22:56:50.856Z] TRACE: myservice/123 on example.com: My message\n', 396 | '\n', 397 | '# extra fields\n', 398 | '\n', 399 | '# bogus\n', 400 | 'not a JSON line\n', 401 | '{"hi": "there"}\n' 402 | ].join(''); 403 | var cmd = _('%s -c "this.level === TRACE" %s/corpus/all.log', 404 | BUNYAN, __dirname); 405 | exec(cmd, function (err, stdout, stderr) { 406 | t.ifError(err); 407 | t.equal(stdout, expect); 408 | t.end(); 409 | }); 410 | }); 411 | 412 | // multiple 413 | test('multiple --conditions', function (t) { 414 | var expect = [ 415 | '# levels\n', 416 | /* JSSTYLED */ 417 | '[2012-02-08T22:56:53.856Z] WARN: myservice/123 on example.com: My message\n', 418 | '\n', 419 | '# extra fields\n', 420 | '\n', 421 | '# bogus\n', 422 | 'not a JSON line\n', 423 | '{"hi": "there"}\n' 424 | ].join(''); 425 | exec(_('%s %s/corpus/all.log -c "this.level === 40" -c "this.pid === 123"', 426 | BUNYAN, __dirname), function (err, stdout, stderr) { 427 | t.ifError(err); 428 | t.equal(stdout, expect); 429 | t.end(); 430 | }); 431 | }); 432 | 433 | // https://github.com/trentm/node-bunyan/issues/30 434 | // 435 | // One of the records in corpus/withreq.log has a 'req' 436 | // field with no 'headers'. Ditto for the 'res' field. 437 | test('robust req handling', function (t) { 438 | var expect = [ 439 | /* BEGIN JSSTYLED */ 440 | '[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==)', 441 | '[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=*)', 442 | ' HEAD /agentprobes?agent=ccf92af9-0b24-46b6-ab60-65095fdd3037 HTTP/1.1', 443 | ' accept: application/json', 444 | ' content-type: application/json', 445 | ' host: 10.2.207.16', 446 | ' connection: keep-alive', 447 | ' --', 448 | ' HTTP/1.1 200 OK', 449 | ' content-md5: 11FxOYiYfpMxmANj4kGJzg==', 450 | ' access-control-allow-origin: *', 451 | ' access-control-allow-headers: Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version', 452 | ' access-control-allow-methods: HEAD', 453 | ' access-control-expose-headers: X-Api-Version, X-Request-Id, X-Response-Time', 454 | ' connection: Keep-Alive', 455 | ' date: Wed, 08 Aug 2012 10:25:47 GMT', 456 | ' server: Amon Master/1.0.0', 457 | ' x-request-id: cce79d15-ffc2-487c-a4e4-e940bdaac31e', 458 | ' x-response-time: 3', 459 | ' --', 460 | ' route: {', 461 | ' "name": "HeadAgentProbes",', 462 | ' "version": false', 463 | ' }', 464 | '[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=*)', 465 | ' HEAD /agentprobes?agent=ccf92af9-0b24-46b6-ab60-65095fdd3037 HTTP/1.1', 466 | ' --', 467 | ' HTTP/1.1 200 OK', 468 | ' --', 469 | ' route: {', 470 | ' "name": "HeadAgentProbes",', 471 | ' "version": false', 472 | ' }' 473 | /* END JSSTYLED */ 474 | ].join('\n') + '\n'; 475 | exec(_('%s %s/corpus/withreq.log', BUNYAN, __dirname), 476 | function (err, stdout, stderr) { 477 | t.ifError(err); 478 | t.equal(stdout, expect); 479 | t.end(); 480 | }); 481 | }); 482 | 483 | // Some past crashes from issues. 484 | test('should not crash on corpus/old-crashers/*.log', function (t) { 485 | var oldCrashers = fs.readdirSync( 486 | path.resolve(__dirname, 'corpus/old-crashers')) 487 | .filter(function (f) { return f.slice(-4) === '.log'; }); 488 | vasync.forEachPipeline({ 489 | inputs: oldCrashers, 490 | func: function (logName, next) { 491 | exec(_('%s %s/corpus/old-crashers/%s', BUNYAN, __dirname, logName), 492 | function (err, stdout, stderr) { 493 | next(err); 494 | }); 495 | } 496 | }, function (err, results) { 497 | t.ifError(err); 498 | t.end(); 499 | }); 500 | }); 501 | 502 | 503 | test('should only show nonempty response bodies', function (t) { 504 | var expect = [ 505 | /* BEGIN JSSTYLED */ 506 | '[2016-02-10T07:28:41.419Z] INFO: myservice/123 on example.com: UnauthorizedError', 507 | ' HTTP/1.1 401 Unauthorized', 508 | ' content-type: text/plain', 509 | ' date: Sat, 07 Mar 2015 06:58:43 GMT', 510 | '[2016-02-10T07:28:41.419Z] INFO: myservice/123 on example.com: hello', 511 | ' HTTP/1.1 200 OK', 512 | ' content-type: text/plain', 513 | ' content-length: 0', 514 | ' date: Sat, 07 Mar 2015 06:58:43 GMT', 515 | ' ', 516 | ' hello', 517 | '[2016-02-10T07:28:41.419Z] INFO: myservice/123 on example.com: UnauthorizedError', 518 | ' HTTP/1.1 401 Unauthorized', 519 | ' content-type: text/plain', 520 | ' date: Sat, 07 Mar 2015 06:58:43 GMT' 521 | /* END JSSTYLED */ 522 | ].join('\n') + '\n'; 523 | exec(_('%s %s/corpus/content-length-0-res.log', BUNYAN, __dirname), 524 | function (err, stdout, stderr) { 525 | t.ifError(err); 526 | t.equal(stdout, expect); 527 | t.end(); 528 | }); 529 | }); 530 | -------------------------------------------------------------------------------- /test/corpus/all.log: -------------------------------------------------------------------------------- 1 | # levels 2 | {"name":"myservice","pid":123,"hostname":"example.com","level":10,"msg":"My message","time":"2012-02-08T22:56:50.856Z","v":0} 3 | {"name":"myservice","pid":123,"hostname":"example.com","level":20,"msg":"My message","time":"2012-02-08T22:56:51.856Z","v":0} 4 | {"name":"myservice","pid":123,"hostname":"example.com","level":30,"msg":"My message","time":"2012-02-08T22:56:52.856Z","v":0} 5 | {"name":"myservice","pid":123,"hostname":"example.com","level":40,"msg":"My message","time":"2012-02-08T22:56:53.856Z","v":0} 6 | {"name":"myservice","pid":123,"hostname":"example.com","level":50,"msg":"My message","time":"2012-02-08T22:56:54.856Z","v":0} 7 | {"name":"myservice","pid":123,"hostname":"example.com","level":55,"msg":"My message","time":"2012-02-08T22:56:55.856Z","v":0} 8 | {"name":"myservice","pid":123,"hostname":"example.com","level":60,"msg":"My message","time":"2012-02-08T22:56:56.856Z","v":0} 9 | 10 | # extra fields 11 | {"name":"myservice","pid":123,"hostname":"example.com","level":30,"one":"short","msg":"My message","time":"2012-02-08T22:56:52.856Z","v":0} 12 | {"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} 13 | {"name":"myservice","pid":123,"hostname":"example.com","level":30,"three":"multi\nline","msg":"My message","time":"2012-02-08T22:56:52.856Z","v":0} 14 | {"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} 15 | {"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} 16 | {"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} 17 | 18 | # bogus 19 | not a JSON line 20 | {"hi": "there"} 21 | -------------------------------------------------------------------------------- /test/corpus/bogus.log: -------------------------------------------------------------------------------- 1 | not a JSON line 2 | {"hi": "there"} 3 | -------------------------------------------------------------------------------- /test/corpus/client-req-with-address.log: -------------------------------------------------------------------------------- 1 | {"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} 2 | -------------------------------------------------------------------------------- /test/corpus/clientreqres.log: -------------------------------------------------------------------------------- 1 | {"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} 2 | {"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} 3 | -------------------------------------------------------------------------------- /test/corpus/content-length-0-res.log: -------------------------------------------------------------------------------- 1 | {"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} 2 | {"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} 3 | {"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} 4 | -------------------------------------------------------------------------------- /test/corpus/extrafield.log: -------------------------------------------------------------------------------- 1 | {"name":"myservice","pid":123,"hostname":"example.com","level":30,"extra":"field","msg":"My message","time":"2012-02-08T22:56:52.856Z","v":0} 2 | -------------------------------------------------------------------------------- /test/corpus/log1.log: -------------------------------------------------------------------------------- 1 | {"name":"agent1","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T16:57:55.586Z","v":0} 2 | {"name":"agent1","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T17:02:49.339Z","v":0} 3 | {"name":"agent1","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T17:02:49.404Z","v":0} 4 | {"name":"agent1","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T17:02:49.404Z","v":0} 5 | -------------------------------------------------------------------------------- /test/corpus/log1.log.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trentm/node-bunyan/5c2258ecb1d33ba34bd7fbd6167e33023dc06e40/test/corpus/log1.log.gz -------------------------------------------------------------------------------- /test/corpus/log2.log: -------------------------------------------------------------------------------- 1 | {"name":"agent2","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T16:58:55.586Z","v":0} 2 | {"name":"agent2","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T17:01:49.339Z","v":0} 3 | {"name":"agent2","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T17:02:47.404Z","v":0} 4 | {"name":"agent2","pid":73267,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T17:02:57.404Z","v":0} 5 | {"name":"agent2","pid":76156,"hostname":"headnode","level":30,"msg":"message","time":"2012-05-08T17:08:01.105Z","v":0} 6 | -------------------------------------------------------------------------------- /test/corpus/non-object-res.log: -------------------------------------------------------------------------------- 1 | {"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} 2 | -------------------------------------------------------------------------------- /test/corpus/old-crashers/139.log: -------------------------------------------------------------------------------- 1 | {"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} 2 | -------------------------------------------------------------------------------- /test/corpus/old-crashers/144.log: -------------------------------------------------------------------------------- 1 | {"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} 2 | -------------------------------------------------------------------------------- /test/corpus/old-crashers/233.log: -------------------------------------------------------------------------------- 1 | {"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} 2 | -------------------------------------------------------------------------------- /test/corpus/old-crashers/242.log: -------------------------------------------------------------------------------- 1 | {"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} 2 | -------------------------------------------------------------------------------- /test/corpus/old-crashers/244.log: -------------------------------------------------------------------------------- 1 | {"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} 2 | -------------------------------------------------------------------------------- /test/corpus/old-crashers/README.md: -------------------------------------------------------------------------------- 1 | Log lines that used to crash `bunyan`. 2 | Typically the file name is the bunyan issue number. 3 | -------------------------------------------------------------------------------- /test/corpus/res-header.log: -------------------------------------------------------------------------------- 1 | {"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} 2 | -------------------------------------------------------------------------------- /test/corpus/res-without-header.log: -------------------------------------------------------------------------------- 1 | {"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} 2 | -------------------------------------------------------------------------------- /test/corpus/simple.log: -------------------------------------------------------------------------------- 1 | {"name":"myservice","pid":123,"hostname":"example.com","level":30,"msg":"My message","time":"2012-02-08T22:56:52.856Z","v":0} 2 | -------------------------------------------------------------------------------- /test/corpus/withreq.log: -------------------------------------------------------------------------------- 1 | {"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} 2 | {"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} 3 | {"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} 4 | -------------------------------------------------------------------------------- /test/ctor.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Trent Mick 3 | * 4 | * Test type checking on creation of the Logger. 5 | */ 6 | 7 | var test = require('tap').test; 8 | 9 | var bunyan = require('../lib/bunyan') 10 | var Logger = bunyan; 11 | 12 | 13 | test('ensure Logger creation options', function (t) { 14 | t.throws(function () { new Logger(); }, 15 | /options \(object\) is required/, 16 | 'no options should throw'); 17 | 18 | t.throws(function () { new Logger({}); }, 19 | /options\.name \(string\) is required/, 20 | 'no options.name should throw'); 21 | 22 | t.doesNotThrow(function () { new Logger({name: 'foo'}); }, 23 | 'just options.name should be sufficient'); 24 | 25 | var options = {name: 'foo', stream: process.stdout, streams: []}; 26 | t.throws(function () { new Logger(options); }, 27 | /* JSSTYLED */ 28 | /cannot mix "streams" and "stream" options/, 29 | 'cannot use "stream" and "streams"'); 30 | 31 | // https://github.com/trentm/node-bunyan/issues/3 32 | options = {name: 'foo', streams: {}}; 33 | t.throws(function () { new Logger(options); }, 34 | /invalid options.streams: must be an array/, 35 | '"streams" must be an array'); 36 | 37 | options = {name: 'foo', serializers: 'a string'}; 38 | t.throws(function () { new Logger(options); }, 39 | /invalid options.serializers: must be an object/, 40 | '"serializers" cannot be a string'); 41 | 42 | options = {name: 'foo', serializers: [1, 2, 3]}; 43 | t.throws(function () { new Logger(options); }, 44 | /invalid options.serializers: must be an object/, 45 | '"serializers" cannot be an array'); 46 | 47 | t.end(); 48 | }); 49 | 50 | 51 | test('ensure Logger constructor is safe without new', function (t) { 52 | t.doesNotThrow(function () { Logger({name: 'foo'}); }, 53 | 'constructor should call self with new if necessary'); 54 | 55 | t.end(); 56 | }); 57 | 58 | 59 | test('ensure Logger creation options (createLogger)', function (t) { 60 | t.throws(function () { bunyan.createLogger(); }, 61 | /options \(object\) is required/, 62 | 'no options should throw'); 63 | 64 | t.throws(function () { bunyan.createLogger({}); }, 65 | /options\.name \(string\) is required/, 66 | 'no options.name should throw'); 67 | 68 | t.doesNotThrow(function () { bunyan.createLogger({name: 'foo'}); }, 69 | 'just options.name should be sufficient'); 70 | 71 | var options = {name: 'foo', stream: process.stdout, streams: []}; 72 | t.throws(function () { bunyan.createLogger(options); }, 73 | /* JSSTYLED */ 74 | /cannot mix "streams" and "stream" options/, 75 | 'cannot use "stream" and "streams"'); 76 | 77 | // https://github.com/trentm/node-bunyan/issues/3 78 | options = {name: 'foo', streams: {}}; 79 | t.throws(function () { bunyan.createLogger(options); }, 80 | /invalid options.streams: must be an array/, 81 | '"streams" must be an array'); 82 | 83 | options = {name: 'foo', serializers: 'a string'}; 84 | t.throws(function () { bunyan.createLogger(options); }, 85 | /invalid options.serializers: must be an object/, 86 | '"serializers" cannot be a string'); 87 | 88 | options = {name: 'foo', serializers: [1, 2, 3]}; 89 | t.throws(function () { bunyan.createLogger(options); }, 90 | /invalid options.serializers: must be an object/, 91 | '"serializers" cannot be an array'); 92 | 93 | t.end(); 94 | }); 95 | 96 | 97 | test('ensure Logger child() options', function (t) { 98 | var log = new Logger({name: 'foo'}); 99 | 100 | t.doesNotThrow(function () { log.child(); }, 101 | 'no options should be fine'); 102 | 103 | t.doesNotThrow(function () { log.child({}); }, 104 | 'empty options should be fine too'); 105 | 106 | t.throws(function () { log.child({name: 'foo'}); }, 107 | /invalid options.name: child cannot set logger name/, 108 | 'child cannot change name'); 109 | 110 | var options = {stream: process.stdout, streams: []}; 111 | t.throws(function () { log.child(options); }, 112 | /* JSSTYLED */ 113 | /cannot mix "streams" and "stream" options/, 114 | 'cannot use "stream" and "streams"'); 115 | 116 | // https://github.com/trentm/node-bunyan/issues/3 117 | options = {streams: {}}; 118 | t.throws(function () { log.child(options); }, 119 | /invalid options.streams: must be an array/, 120 | '"streams" must be an array'); 121 | 122 | options = {serializers: 'a string'}; 123 | t.throws(function () { log.child(options); }, 124 | /invalid options.serializers: must be an object/, 125 | '"serializers" cannot be a string'); 126 | 127 | options = {serializers: [1, 2, 3]}; 128 | t.throws(function () { log.child(options); }, 129 | /invalid options.serializers: must be an object/, 130 | '"serializers" cannot be an array'); 131 | 132 | t.end(); 133 | }); 134 | 135 | 136 | test('ensure Logger() rejects non-Logger parents', function (t) { 137 | var dad = new Logger({name: 'dad', streams: []}); 138 | 139 | t.throws(function () { new Logger({}, {}); }, 140 | /invalid Logger creation: do not pass a second arg/, 141 | 'Logger arguments must be valid'); 142 | 143 | t.doesNotThrow(function () { new Logger(dad, {}); }, 144 | 'Logger allows Logger instance as parent'); 145 | 146 | t.end(); 147 | }); 148 | -------------------------------------------------------------------------------- /test/cycles.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Trent Mick. 3 | * 4 | * Make sure cycles are safe. 5 | */ 6 | 7 | var Logger = require('../lib/bunyan.js'); 8 | var test = require('tap').test; 9 | 10 | 11 | var Stream = require('stream').Stream; 12 | var outstr = new Stream; 13 | outstr.writable = true; 14 | var output = []; 15 | outstr.write = function (c) { 16 | output.push(JSON.parse(c + '')); 17 | }; 18 | outstr.end = function (c) { 19 | if (c) this.write(c); 20 | this.emit('end'); 21 | }; 22 | 23 | var expect = 24 | [ 25 | { 26 | 'name': 'blammo', 27 | 'level': 30, 28 | 'msg': 'bango { bang: \'boom\', KABOOM: [Circular] }', 29 | 'v': 0 30 | }, 31 | { 32 | 'name': 'blammo', 33 | 'level': 30, 34 | 'msg': 'kaboom { bang: \'boom\', KABOOM: [Circular] }', 35 | 'v': 0 36 | }, 37 | { 38 | 'name': 'blammo', 39 | 'level': 30, 40 | 'bang': 'boom', 41 | 'KABOOM': { 42 | 'bang': 'boom', 43 | 'KABOOM': '[Circular]' 44 | }, 45 | 'msg': '', 46 | 'v': 0 47 | } 48 | ]; 49 | 50 | var log = new Logger({ 51 | name: 'blammo', 52 | streams: [ 53 | { 54 | type: 'stream', 55 | level: 'info', 56 | stream: outstr 57 | } 58 | ] 59 | }); 60 | 61 | test('cycles', function (t) { 62 | var rec; 63 | 64 | outstr.on('end', function () { 65 | output.forEach(function (o, i) { 66 | // Drop variable parts for comparison. 67 | delete o.hostname; 68 | delete o.pid; 69 | delete o.time; 70 | 71 | // In change https://github.com/nodejs/node/pull/27685 (part of 72 | // node v14), how objects with circular references are stringified 73 | // with `util.inspect` changed. 74 | if (Number(process.versions.node.split('.')[0]) >= 14) { 75 | expect[i].msg = expect[i].msg.replace( 76 | // JSSTYLED 77 | /{ bang: 'boom', KABOOM: \[Circular\] }/, 78 | ' { bang: \'boom\', KABOOM: [Circular *1] }' 79 | ); 80 | } 81 | 82 | t.equal(JSON.stringify(o), JSON.stringify(expect[i]), 83 | 'log record ' + i + ' matches'); 84 | }); 85 | t.end(); 86 | }); 87 | 88 | var obj = { bang: 'boom' }; 89 | obj.KABOOM = obj; // This creates a circular reference. 90 | 91 | log.info('bango', obj); 92 | log.info('kaboom', obj.KABOOM); 93 | log.info(obj); 94 | 95 | t.ok('did not throw'); 96 | 97 | outstr.end(); 98 | }); 99 | -------------------------------------------------------------------------------- /test/dtrace/dtrace.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Trent Mick 3 | * 4 | * If available, test dtrace support. 5 | */ 6 | 7 | var spawn = require('child_process').spawn; 8 | var format = require('util').format; 9 | var test = require('tap').test; 10 | 11 | var bunyan = require('../../lib/bunyan'); 12 | 13 | 14 | // Determine if we can run the dtrace tests. 15 | var dtracePlats = ['sunos', 'darwin', 'freebsd']; 16 | var runDtraceTests = true; 17 | try { 18 | require('dtrace-provider'); 19 | } catch (e) { 20 | console.log('# skip dtrace tests: no dtrace-provider module'); 21 | runDtraceTests = false; 22 | } 23 | if (!runDtraceTests) { 24 | /* pass through */ 25 | } else if (dtracePlats.indexOf(process.platform) === -1) { 26 | console.log('# skip dtrace tests: not on a platform with dtrace'); 27 | runDtraceTests = false; 28 | } else if (process.env.SKIP_DTRACE) { 29 | console.log('# skip dtrace tests: SKIP_DTRACE envvar set'); 30 | runDtraceTests = false; 31 | } else if (process.getgid() !== 0) { 32 | console.log('# skip dtrace tests: gid is not 0, run with `sudo`'); 33 | runDtraceTests = false; 34 | } 35 | if (runDtraceTests) { 36 | 37 | 38 | test('basic dtrace', function (t) { 39 | var argv = ['dtrace', '-Z', '-x', 'strsize=4k', '-qn', 40 | 'bunyan$target:::log-*{printf("%s", copyinstr(arg0))}', 41 | '-c', format('node %s/log-some.js', __dirname)]; 42 | var dtrace = spawn(argv[0], argv.slice(1)); 43 | //console.error('ARGV: %j', argv); 44 | //console.error('CMD: %s', argv.join(' ')); 45 | 46 | var traces = []; 47 | dtrace.stdout.on('data', function (data) { 48 | //console.error('DTRACE STDOUT:', data.toString()); 49 | traces.push(data.toString()); 50 | }); 51 | dtrace.stderr.on('data', function (data) { 52 | console.error('DTRACE STDERR:', data.toString()); 53 | }); 54 | dtrace.on('exit', function (code) { 55 | t.notOk(code, 'dtrace exited cleanly'); 56 | traces = traces.join('').split('\n') 57 | .filter(function (t) { return t.trim().length }) 58 | .map(function (t) { return JSON.parse(t) }); 59 | t.equal(traces.length, 2, 'got 2 log records'); 60 | if (traces.length) { 61 | t.equal(traces[0].level, bunyan.DEBUG); 62 | t.equal(traces[0].foo, 'bar'); 63 | t.equal(traces[1].level, bunyan.TRACE); 64 | t.equal(traces[1].msg, 'hi at trace'); 65 | } 66 | t.end(); 67 | }); 68 | }); 69 | 70 | 71 | /* 72 | * Run a logger that logs a couple records every second. 73 | * Then run `bunyan -p PID` to capture. 74 | * Let those run for a few seconds to ensure dtrace has time to attach and 75 | * capture something. 76 | */ 77 | test('bunyan -p', function (t) { 78 | var p = spawn('node', [__dirname + '/log-some-loop.js']); 79 | 80 | var bunyanP = spawn('node', 81 | [__dirname + '/../bin/bunyan', '-p', String(p.pid), '-0']); 82 | var traces = []; 83 | bunyanP.stdout.on('data', function (data) { 84 | //console.error('BUNYAN -P STDOUT:', data.toString()); 85 | traces.push(data.toString()); 86 | }); 87 | bunyanP.stderr.on('data', function (data) { 88 | console.error('BUNYAN -P STDERR:', data.toString()); 89 | }); 90 | bunyanP.on('exit', function (code) { 91 | traces = traces.join('').split('\n') 92 | .filter(function (t) { return t.trim().length }) 93 | .map(function (t) { return JSON.parse(t) }); 94 | t.ok(traces.length >= 3, 'got >=3 log records: ' + traces.length); 95 | if (traces.length >= 3) { 96 | if (traces[0].level !== bunyan.DEBUG) { 97 | traces.shift(); 98 | } 99 | t.equal(traces[0].level, bunyan.DEBUG); 100 | t.equal(traces[0].foo, 'bar'); 101 | t.equal(traces[1].level, bunyan.TRACE); 102 | t.equal(traces[1].msg, 'hi at trace'); 103 | } 104 | t.end(); 105 | }); 106 | 107 | // Give it a few seconds to ensure we get some traces. 108 | setTimeout(function () { 109 | p.kill(); 110 | bunyanP.kill(); 111 | }, 5000); 112 | }); 113 | 114 | 115 | } /* end of `if (runDtraceTests)` */ 116 | -------------------------------------------------------------------------------- /test/error-event.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Trent Mick 3 | * 4 | * Test emission and handling of 'error' event in a logger with a 'path' 5 | * stream. 6 | */ 7 | 8 | var EventEmitter = require('events').EventEmitter; 9 | var test = require('tap').test; 10 | var util = require('util'); 11 | 12 | var bunyan = require('../lib/bunyan'); 13 | 14 | 15 | var BOGUS_PATH = '/this/path/is/bogus.log'; 16 | 17 | test('error event on file stream (reemitErrorEvents=undefined)', function (t) { 18 | var log = bunyan.createLogger( 19 | {name: 'error-event-1', streams: [ {path: BOGUS_PATH} ]}); 20 | log.on('error', function (err, stream) { 21 | t.ok(err, 'got err in error event: ' + err); 22 | t.equal(err.code, 'ENOENT', 'error code is ENOENT'); 23 | t.ok(stream, 'got a stream argument'); 24 | t.equal(stream.path, BOGUS_PATH); 25 | t.equal(stream.type, 'file'); 26 | t.end(); 27 | }); 28 | log.info('info log message'); 29 | }); 30 | 31 | test('error event on file stream (reemitErrorEvents=true)', function (t) { 32 | var log = bunyan.createLogger({ 33 | name: 'error-event-2', 34 | streams: [ { 35 | path: BOGUS_PATH, 36 | reemitErrorEvents: true 37 | } ] 38 | }); 39 | log.on('error', function (err, stream) { 40 | t.ok(err, 'got err in error event: ' + err); 41 | t.equal(err.code, 'ENOENT', 'error code is ENOENT'); 42 | t.ok(stream, 'got a stream argument'); 43 | t.equal(stream.path, BOGUS_PATH); 44 | t.equal(stream.type, 'file'); 45 | t.end(); 46 | }); 47 | log.info('info log message'); 48 | }); 49 | 50 | test('error event on file stream (reemitErrorEvents=false)', 51 | function (t) { 52 | var log = bunyan.createLogger({ 53 | name: 'error-event-3', 54 | streams: [ { 55 | path: BOGUS_PATH, 56 | reemitErrorEvents: false 57 | } ] 58 | }); 59 | // Hack into the underlying created file stream to catch the error event. 60 | log.streams[0].stream.on('error', function (err) { 61 | t.ok(err, 'got error event on the file stream'); 62 | t.end(); 63 | }); 64 | log.on('error', function (err, stream) { 65 | t.fail('should not have gotten error event on logger'); 66 | t.end(); 67 | }); 68 | log.info('info log message'); 69 | }); 70 | 71 | 72 | function MyErroringStream() {} 73 | util.inherits(MyErroringStream, EventEmitter); 74 | MyErroringStream.prototype.write = function (rec) { 75 | this.emit('error', new Error('boom')); 76 | } 77 | 78 | test('error event on raw stream (reemitErrorEvents=undefined)', function (t) { 79 | var estream = new MyErroringStream(); 80 | var log = bunyan.createLogger({ 81 | name: 'error-event-raw', 82 | streams: [ 83 | { 84 | stream: estream, 85 | type: 'raw' 86 | } 87 | ] 88 | }); 89 | estream.on('error', function (err) { 90 | t.ok(err, 'got error event on the raw stream'); 91 | t.end(); 92 | }); 93 | log.on('error', function (err, stream) { 94 | t.fail('should not have gotten error event on logger'); 95 | t.end(); 96 | }); 97 | log.info('info log message'); 98 | }); 99 | 100 | test('error event on raw stream (reemitErrorEvents=false)', function (t) { 101 | var estream = new MyErroringStream(); 102 | var log = bunyan.createLogger({ 103 | name: 'error-event-raw', 104 | streams: [ 105 | { 106 | stream: estream, 107 | type: 'raw', 108 | reemitErrorEvents: false 109 | } 110 | ] 111 | }); 112 | estream.on('error', function (err) { 113 | t.ok(err, 'got error event on the raw stream'); 114 | t.end(); 115 | }); 116 | log.on('error', function (err, stream) { 117 | t.fail('should not have gotten error event on logger'); 118 | t.end(); 119 | }); 120 | log.info('info log message'); 121 | }); 122 | 123 | test('error event on raw stream (reemitErrorEvents=true)', function (t) { 124 | var estream = new MyErroringStream(); 125 | var log = bunyan.createLogger({ 126 | name: 'error-event-raw', 127 | streams: [ 128 | { 129 | stream: estream, 130 | type: 'raw', 131 | reemitErrorEvents: true 132 | } 133 | ] 134 | }); 135 | log.on('error', function (err, stream) { 136 | t.ok(err, 'got err in error event: ' + err); 137 | t.equal(err.message, 'boom'); 138 | t.ok(stream, 'got a stream argument'); 139 | t.ok(stream.stream instanceof MyErroringStream); 140 | t.equal(stream.type, 'raw'); 141 | t.end(); 142 | }); 143 | log.info('info log message'); 144 | }); 145 | -------------------------------------------------------------------------------- /test/level.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Trent Mick 3 | * 4 | * Test the `log.level(...)`. 5 | */ 6 | 7 | var test = require('tap').test; 8 | var util = require('util'), 9 | format = util.format, 10 | inspect = util.inspect; 11 | var p = console.log; 12 | 13 | var bunyan = require('../lib/bunyan'); 14 | 15 | 16 | // ---- test boolean `log.()` calls 17 | 18 | var log1 = bunyan.createLogger({ 19 | name: 'log1', 20 | streams: [ 21 | { 22 | path: __dirname + '/level.test.log1.log', 23 | level: 'info' 24 | } 25 | ] 26 | }); 27 | 28 | 29 | test('log.level() -> level num', function (t) { 30 | t.equal(log1.level(), bunyan.INFO); 31 | t.end(); 32 | }); 33 | 34 | test('log.level()', function (t) { 35 | log1.level(bunyan.DEBUG); 36 | t.equal(log1.level(), bunyan.DEBUG); 37 | t.end(); 38 | }); 39 | 40 | test('log.level()', function (t) { 41 | log1.level(10); 42 | t.equal(log1.level(), bunyan.TRACE); 43 | t.end(); 44 | }); 45 | 46 | test('log.level()', function (t) { 47 | log1.level('error'); 48 | t.equal(log1.level(), bunyan.ERROR); 49 | t.end(); 50 | }); 51 | 52 | // A trick to turn logging off. 53 | // See . 54 | test('log.level(FATAL + 1)', function (t) { 55 | log1.level(bunyan.FATAL + 1); 56 | t.equal(log1.level(), bunyan.FATAL + 1); 57 | t.end(); 58 | }); 59 | 60 | test('log.level()', function (t) { 61 | log1.level(0); 62 | t.equal(log1.level(), 0); 63 | log1.level(Number.MAX_VALUE); 64 | t.equal(log1.level(), Number.MAX_VALUE); 65 | log1.level(Infinity); 66 | t.equal(log1.level(), Infinity); 67 | t.end(); 68 | }); 69 | 70 | test('log.level()', function (t) { 71 | t.throws(function () { 72 | var log = bunyan.createLogger({name: 'invalid', level: 'booga'}); 73 | // JSSTYLED 74 | }, /unknown level name: "booga"/); 75 | t.throws(function () { 76 | var log = bunyan.createLogger({name: 'invalid', level: []}); 77 | }, /cannot resolve level: invalid arg \(object\): \[\]/); 78 | t.throws(function () { 79 | var log = bunyan.createLogger({name: 'invalid', level: true}); 80 | }, /cannot resolve level: invalid arg \(boolean\): true/); 81 | t.throws(function () { 82 | var log = bunyan.createLogger({name: 'invalid', level: -1}); 83 | }, /level is not a positive integer: -1/); 84 | t.throws(function () { 85 | var log = bunyan.createLogger({name: 'invalid', level: 3.14}); 86 | }, /level is not a positive integer: 3.14/); 87 | t.throws(function () { 88 | var log = bunyan.createLogger({name: 'invalid', level: -Infinity}); 89 | }, /level is not a positive integer: -Infinity/); 90 | t.end(); 91 | }); 92 | -------------------------------------------------------------------------------- /test/log-some-loop.js: -------------------------------------------------------------------------------- 1 | 2 | // A helper script to log a few times, pause, repeat. We attempt to NOT emit 3 | // to stdout or stderr because this is used for dtrace testing 4 | // and we don't want to mix output. 5 | 6 | var bunyan = require('../lib/bunyan'); 7 | var log = bunyan.createLogger({ 8 | name: 'play', 9 | serializers: bunyan.stdSerializers 10 | }); 11 | 12 | setInterval(function logSome() { 13 | log.debug({foo: 'bar'}, 'hi at debug') 14 | log.trace('hi at trace') 15 | }, 1000); 16 | -------------------------------------------------------------------------------- /test/log-some.js: -------------------------------------------------------------------------------- 1 | 2 | // A helper script to log a few times. We attempt to NOT emit 3 | // to stdout or stderr because this is used for dtrace testing 4 | // and we don't want to mix output. 5 | 6 | var bunyan = require('../lib/bunyan'); 7 | var log = bunyan.createLogger({ 8 | name: 'play', 9 | serializers: bunyan.stdSerializers 10 | }); 11 | log.debug({foo: 'bar'}, 'hi at debug') 12 | log.trace('hi at trace') 13 | -------------------------------------------------------------------------------- /test/log.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Trent Mick 3 | * 4 | * Test the `log.trace(...)`, `log.debug(...)`, ..., `log.fatal(...)` API. 5 | */ 6 | 7 | var util = require('util'), 8 | format = util.format, 9 | inspect = util.inspect; 10 | var test = require('tap').test; 11 | var p = console.log; 12 | 13 | var bunyan = require('../lib/bunyan'); 14 | 15 | 16 | // ---- test boolean `log.()` calls 17 | 18 | var log1 = bunyan.createLogger({ 19 | name: 'log1', 20 | streams: [ 21 | { 22 | path: __dirname + '/log.test.log1.log', 23 | level: 'info' 24 | } 25 | ] 26 | }); 27 | 28 | var log2 = bunyan.createLogger({ 29 | name: 'log2', 30 | streams: [ 31 | { 32 | path: __dirname + '/log.test.log2a.log', 33 | level: 'error' 34 | }, 35 | { 36 | path: __dirname + '/log.test.log2b.log', 37 | level: 'debug' 38 | } 39 | ] 40 | }) 41 | 42 | test('log.LEVEL() -> boolean', function (t) { 43 | t.equal(log1.trace(), false, 'log1.trace() is false') 44 | t.equal(log1.debug(), false) 45 | t.equal(log1.info(), true) 46 | t.equal(log1.warn(), true) 47 | t.equal(log1.error(), true) 48 | t.equal(log1.fatal(), true) 49 | 50 | // Level is the *lowest* level of all streams. 51 | t.equal(log2.trace(), false) 52 | t.equal(log2.debug(), true) 53 | t.equal(log2.info(), true) 54 | t.equal(log2.warn(), true) 55 | t.equal(log2.error(), true) 56 | t.equal(log2.fatal(), true) 57 | t.end(); 58 | }); 59 | 60 | 61 | // ---- test `log.(...)` calls which various input types 62 | 63 | function Catcher() { 64 | this.records = []; 65 | } 66 | Catcher.prototype.write = function (record) { 67 | this.records.push(record); 68 | } 69 | var catcher = new Catcher(); 70 | var log3 = new bunyan.createLogger({ 71 | name: 'log3', 72 | streams: [ 73 | { 74 | type: 'raw', 75 | stream: catcher, 76 | level: 'trace' 77 | } 78 | ] 79 | }); 80 | 81 | var names = ['trace', 'debug', 'info', 'warn', 'error', 'fatal']; 82 | var fields = {one: 'un'}; 83 | 84 | test('log.info(undefined, )', function (t) { 85 | // https://github.com/nodejs/node/pull/23162 (starting in node v12) changed 86 | // util.format() handling such that this test case expected string differs. 87 | var expect; 88 | if (Number(process.versions.node.split('.')[0]) >= 12) { 89 | expect = 'undefined some message'; 90 | } else { 91 | expect = 'undefined \'some message\''; 92 | } 93 | 94 | names.forEach(function (lvl) { 95 | log3[lvl].call(log3, undefined, 'some message'); 96 | var rec = catcher.records[catcher.records.length - 1]; 97 | t.equal(rec.msg, expect, 98 | format('log.%s(undefined, "some message")', lvl)); 99 | }); 100 | t.end(); 101 | }); 102 | 103 | test('log.info(, undefined)', function (t) { 104 | names.forEach(function (lvl) { 105 | log3[lvl].call(log3, fields, undefined); 106 | var rec = catcher.records[catcher.records.length - 1]; 107 | t.equal(rec.msg, 'undefined', 108 | format('log.%s msg: expect "undefined", got %j', lvl, rec.msg)); 109 | t.equal(rec.one, 'un'); 110 | }); 111 | t.end(); 112 | }); 113 | 114 | test('log.info(null, )', function (t) { 115 | names.forEach(function (lvl) { 116 | log3[lvl].call(log3, null, 'some message'); 117 | var rec = catcher.records[catcher.records.length - 1]; 118 | t.equal(rec.msg, 'some message', 119 | format('log.%s msg is "some message"', lvl)); 120 | }); 121 | t.end(); 122 | }); 123 | 124 | test('log.info(, null)', function (t) { 125 | names.forEach(function (lvl) { 126 | log3[lvl].call(log3, fields, null); 127 | var rec = catcher.records[catcher.records.length - 1]; 128 | t.equal(rec.msg, 'null', 129 | format('log.%s msg: expect "null", got %j', lvl, rec.msg)); 130 | t.equal(rec.one, 'un'); 131 | }); 132 | t.end(); 133 | }); 134 | 135 | test('log.info()', function (t) { 136 | names.forEach(function (lvl) { 137 | log3[lvl].call(log3, 'some message'); 138 | var rec = catcher.records[catcher.records.length - 1]; 139 | t.equal(rec.msg, 'some message', 140 | format('log.%s msg is "some message"', lvl)); 141 | }); 142 | t.end(); 143 | }); 144 | 145 | test('log.info(, )', function (t) { 146 | names.forEach(function (lvl) { 147 | log3[lvl].call(log3, fields, 'some message'); 148 | var rec = catcher.records[catcher.records.length - 1]; 149 | t.equal(rec.msg, 'some message', 150 | format('log.%s msg: got %j', lvl, rec.msg)); 151 | t.equal(rec.one, 'un'); 152 | }); 153 | t.end(); 154 | }); 155 | 156 | test('log.info()', function (t) { 157 | names.forEach(function (lvl) { 158 | log3[lvl].call(log3, true); 159 | var rec = catcher.records[catcher.records.length - 1]; 160 | t.equal(rec.msg, 'true', 161 | format('log.%s msg is "true"', lvl)); 162 | }); 163 | t.end(); 164 | }); 165 | 166 | test('log.info(, )', function (t) { 167 | names.forEach(function (lvl) { 168 | log3[lvl].call(log3, fields, true); 169 | var rec = catcher.records[catcher.records.length - 1]; 170 | t.equal(rec.msg, 'true', 171 | format('log.%s msg: got %j', lvl, rec.msg)); 172 | t.equal(rec.one, 'un'); 173 | }); 174 | t.end(); 175 | }); 176 | 177 | test('log.info()', function (t) { 178 | names.forEach(function (lvl) { 179 | log3[lvl].call(log3, 3.14); 180 | var rec = catcher.records[catcher.records.length - 1]; 181 | t.equal(rec.msg, '3.14', 182 | format('log.%s msg: got %j', lvl, rec.msg)); 183 | }); 184 | t.end(); 185 | }); 186 | 187 | test('log.info(, )', function (t) { 188 | names.forEach(function (lvl) { 189 | log3[lvl].call(log3, fields, 3.14); 190 | var rec = catcher.records[catcher.records.length - 1]; 191 | t.equal(rec.msg, '3.14', 192 | format('log.%s msg: got %j', lvl, rec.msg)); 193 | t.equal(rec.one, 'un'); 194 | }); 195 | t.end(); 196 | }); 197 | 198 | test('log.info()', function (t) { 199 | var func = function func1() {}; 200 | names.forEach(function (lvl) { 201 | log3[lvl].call(log3, func); 202 | var rec = catcher.records[catcher.records.length - 1]; 203 | t.equal(rec.msg, '[Function: func1]', 204 | format('log.%s msg: got %j', lvl, rec.msg)); 205 | }); 206 | t.end(); 207 | }); 208 | 209 | test('log.info(, )', function (t) { 210 | var func = function func2() {}; 211 | names.forEach(function (lvl) { 212 | log3[lvl].call(log3, fields, func); 213 | var rec = catcher.records[catcher.records.length - 1]; 214 | t.equal(rec.msg, '[Function: func2]', 215 | format('log.%s msg: got %j', lvl, rec.msg)); 216 | t.equal(rec.one, 'un'); 217 | }); 218 | t.end(); 219 | }); 220 | 221 | test('log.info()', function (t) { 222 | var arr = ['a', 1, {two: 'deux'}]; 223 | names.forEach(function (lvl) { 224 | log3[lvl].call(log3, arr); 225 | var rec = catcher.records[catcher.records.length - 1]; 226 | t.equal(rec.msg, format(arr), 227 | format('log.%s msg: got %j', lvl, rec.msg)); 228 | }); 229 | t.end(); 230 | }); 231 | 232 | test('log.info(, )', function (t) { 233 | var arr = ['a', 1, {two: 'deux'}]; 234 | names.forEach(function (lvl) { 235 | log3[lvl].call(log3, fields, arr); 236 | var rec = catcher.records[catcher.records.length - 1]; 237 | t.equal(rec.msg, format(arr), 238 | format('log.%s msg: got %j', lvl, rec.msg)); 239 | t.equal(rec.one, 'un'); 240 | }); 241 | t.end(); 242 | }); 243 | 244 | 245 | /* 246 | * By accident (starting with trentm/node-bunyan#85 in bunyan@0.23.0), 247 | * log.info(null, ...) 248 | * was interpreted as `null` being the object of fields. It is gracefully 249 | * handled, which is good. However, had I to do it again, I would have made 250 | * that interpret `null` as the *message*, and no fields having been passed. 251 | * I think it is baked now. It would take a major bunyan rev to change it, 252 | * but I don't think it is worth it: passing `null` as the first arg isn't 253 | * really an intended way to call these Bunyan methods for either case. 254 | */ 255 | 256 | test('log.info(null)', function (t) { 257 | names.forEach(function (lvl) { 258 | log3[lvl].call(log3, null); 259 | var rec = catcher.records[catcher.records.length - 1]; 260 | t.equal(rec.msg, '', format('log.%s msg: got %j', lvl, rec.msg)); 261 | }); 262 | t.end(); 263 | }); 264 | 265 | test('log.info(null, )', function (t) { 266 | names.forEach(function (lvl) { 267 | log3[lvl].call(log3, null, 'my message'); 268 | var rec = catcher.records[catcher.records.length - 1]; 269 | t.equal(rec.msg, 'my message', 270 | format('log.%s msg: got %j', lvl, rec.msg)); 271 | }); 272 | t.end(); 273 | }); 274 | -------------------------------------------------------------------------------- /test/other-api.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Trent Mick 3 | * 4 | * Test other parts of the exported API. 5 | */ 6 | 7 | var test = require('tap').test; 8 | 9 | var bunyan = require('../lib/bunyan'); 10 | 11 | 12 | test('bunyan.s', function (t) { 13 | t.ok(bunyan.TRACE, 'TRACE'); 14 | t.ok(bunyan.DEBUG, 'DEBUG'); 15 | t.ok(bunyan.INFO, 'INFO'); 16 | t.ok(bunyan.WARN, 'WARN'); 17 | t.ok(bunyan.ERROR, 'ERROR'); 18 | t.ok(bunyan.FATAL, 'FATAL'); 19 | t.end(); 20 | }); 21 | 22 | test('bunyan.resolveLevel()', function (t) { 23 | t.equal(bunyan.resolveLevel('trace'), bunyan.TRACE, 'TRACE'); 24 | t.equal(bunyan.resolveLevel('TRACE'), bunyan.TRACE, 'TRACE'); 25 | t.equal(bunyan.resolveLevel('debug'), bunyan.DEBUG, 'DEBUG'); 26 | t.equal(bunyan.resolveLevel('DEBUG'), bunyan.DEBUG, 'DEBUG'); 27 | t.equal(bunyan.resolveLevel('info'), bunyan.INFO, 'INFO'); 28 | t.equal(bunyan.resolveLevel('INFO'), bunyan.INFO, 'INFO'); 29 | t.equal(bunyan.resolveLevel('warn'), bunyan.WARN, 'WARN'); 30 | t.equal(bunyan.resolveLevel('WARN'), bunyan.WARN, 'WARN'); 31 | t.equal(bunyan.resolveLevel('error'), bunyan.ERROR, 'ERROR'); 32 | t.equal(bunyan.resolveLevel('ERROR'), bunyan.ERROR, 'ERROR'); 33 | t.equal(bunyan.resolveLevel('fatal'), bunyan.FATAL, 'FATAL'); 34 | t.equal(bunyan.resolveLevel('FATAL'), bunyan.FATAL, 'FATAL'); 35 | t.end(); 36 | }); 37 | -------------------------------------------------------------------------------- /test/process-exit.js: -------------------------------------------------------------------------------- 1 | var bunyan = require('../lib/bunyan'); 2 | var log = bunyan.createLogger({ 3 | name: 'default', 4 | streams: [ { 5 | type: 'rotating-file', 6 | path: __dirname + '/log.test.rot.log', 7 | period: '1d', 8 | count: 7 9 | } ] 10 | }); 11 | console.log('done'); 12 | -------------------------------------------------------------------------------- /test/process-exit.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* 3 | * Test that bunyan process will terminate. 4 | * 5 | * Note: Currently (bunyan 0.23.1) this fails on node 0.8, because there is 6 | * no `unref` in node 0.8 and bunyan doesn't yet have `Logger.prototype.close()` 7 | * support. 8 | */ 9 | 10 | var exec = require('child_process').exec; 11 | var test = require('tap').test; 12 | 13 | var nodeVer = process.versions.node.split('.').map(Number); 14 | 15 | if (nodeVer[0] <= 0 && nodeVer[1] <= 8) { 16 | console.warn('skip test (node <= 0.8)'); 17 | } else { 18 | test('log with rotating file stream will terminate', function (t) { 19 | exec('node ' +__dirname + '/process-exit.js', {timeout: 1000}, 20 | function (err, stdout, stderr) { 21 | t.ifError(err); 22 | t.equal(stdout, 'done\n'); 23 | t.equal(stderr, ''); 24 | t.end(); 25 | }); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /test/raw-stream.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Trent Mick 3 | * 4 | * Test `type: 'raw'` Logger streams. 5 | */ 6 | 7 | var format = require('util').format; 8 | var test = require('tap').test; 9 | 10 | var Logger = require('../lib/bunyan'); 11 | 12 | 13 | function CapturingStream(recs) { 14 | this.recs = recs; 15 | } 16 | CapturingStream.prototype.write = function (rec) { 17 | this.recs.push(rec); 18 | } 19 | 20 | 21 | test('raw stream', function (t) { 22 | var recs = []; 23 | 24 | var log = new Logger({ 25 | name: 'raw-stream-test', 26 | streams: [ 27 | { 28 | stream: new CapturingStream(recs), 29 | type: 'raw' 30 | } 31 | ] 32 | }); 33 | log.info('first'); 34 | log.info({two: 'deux'}, 'second'); 35 | 36 | t.equal(recs.length, 2); 37 | t.equal(typeof (recs[0]), 'object', 'first rec is an object'); 38 | t.equal(recs[1].two, 'deux', '"two" field made it through'); 39 | t.end(); 40 | }); 41 | 42 | 43 | test('raw streams and regular streams can mix', function (t) { 44 | var rawRecs = []; 45 | var nonRawRecs = []; 46 | 47 | var log = new Logger({ 48 | name: 'raw-stream-test', 49 | streams: [ 50 | { 51 | stream: new CapturingStream(rawRecs), 52 | type: 'raw' 53 | }, 54 | { 55 | stream: new CapturingStream(nonRawRecs) 56 | } 57 | ] 58 | }); 59 | log.info('first'); 60 | log.info({two: 'deux'}, 'second'); 61 | 62 | t.equal(rawRecs.length, 2); 63 | t.equal(typeof (rawRecs[0]), 'object', 'first rawRec is an object'); 64 | t.equal(rawRecs[1].two, 'deux', '"two" field made it through'); 65 | 66 | t.equal(nonRawRecs.length, 2); 67 | t.equal(typeof (nonRawRecs[0]), 'string', 'first nonRawRec is a string'); 68 | 69 | t.end(); 70 | }); 71 | 72 | 73 | test('child adding a non-raw stream works', function (t) { 74 | var parentRawRecs = []; 75 | var rawRecs = []; 76 | var nonRawRecs = []; 77 | 78 | var logParent = new Logger({ 79 | name: 'raw-stream-test', 80 | streams: [ 81 | { 82 | stream: new CapturingStream(parentRawRecs), 83 | type: 'raw' 84 | } 85 | ] 86 | }); 87 | var logChild = logParent.child({ 88 | child: true, 89 | streams: [ 90 | { 91 | stream: new CapturingStream(rawRecs), 92 | type: 'raw' 93 | }, 94 | { 95 | stream: new CapturingStream(nonRawRecs) 96 | } 97 | ] 98 | }); 99 | logParent.info('first'); 100 | logChild.info({two: 'deux'}, 'second'); 101 | 102 | t.equal(rawRecs.length, 1, 103 | format('rawRecs length should be 1 (is %d)', rawRecs.length)); 104 | t.equal(typeof (rawRecs[0]), 'object', 'rawRec entry is an object'); 105 | t.equal(rawRecs[0].two, 'deux', '"two" field made it through'); 106 | 107 | t.equal(nonRawRecs.length, 1); 108 | t.equal(typeof (nonRawRecs[0]), 'string', 'first nonRawRec is a string'); 109 | 110 | t.end(); 111 | }); 112 | -------------------------------------------------------------------------------- /test/ringbuffer.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Test the RingBuffer output stream. 3 | */ 4 | 5 | var test = require('tap').test; 6 | 7 | var Logger = require('../lib/bunyan'); 8 | 9 | var ringbuffer = new Logger.RingBuffer({ 'limit': 5 }); 10 | 11 | var log1 = new Logger({ 12 | name: 'log1', 13 | streams: [ 14 | { 15 | stream: ringbuffer, 16 | type: 'raw', 17 | level: 'info' 18 | } 19 | ] 20 | }); 21 | 22 | test('ringbuffer', function (t) { 23 | log1.info('hello'); 24 | log1.trace('there'); 25 | log1.error('android'); 26 | t.equal(ringbuffer.records.length, 2); 27 | t.equal(ringbuffer.records[0]['msg'], 'hello'); 28 | t.equal(ringbuffer.records[1]['msg'], 'android'); 29 | log1.error('one'); 30 | log1.error('two'); 31 | log1.error('three'); 32 | t.equal(ringbuffer.records.length, 5); 33 | log1.error('four'); 34 | t.equal(ringbuffer.records.length, 5); 35 | t.equal(ringbuffer.records[0]['msg'], 'android'); 36 | t.equal(ringbuffer.records[1]['msg'], 'one'); 37 | t.equal(ringbuffer.records[2]['msg'], 'two'); 38 | t.equal(ringbuffer.records[3]['msg'], 'three'); 39 | t.equal(ringbuffer.records[4]['msg'], 'four'); 40 | t.end(); 41 | }); 42 | -------------------------------------------------------------------------------- /test/safe-json-stringify-1.js: -------------------------------------------------------------------------------- 1 | var bunyan = require('../lib/bunyan'); 2 | 3 | var log = bunyan.createLogger({ 4 | name: 'safe-json-stringify-1' 5 | }); 6 | 7 | var obj = {}; 8 | obj.__defineGetter__('boom', 9 | function () { throw new Error('__defineGetter__ ouch!'); }); 10 | log.info({obj: obj}, 'using __defineGetter__'); 11 | -------------------------------------------------------------------------------- /test/safe-json-stringify-2.js: -------------------------------------------------------------------------------- 1 | process.env.BUNYAN_TEST_NO_SAFE_JSON_STRINGIFY = '1'; 2 | var bunyan = require('../lib/bunyan'); 3 | 4 | var log = bunyan.createLogger({ 5 | name: 'safe-json-stringify-2' 6 | }); 7 | 8 | var obj = {}; 9 | obj.__defineGetter__('boom', 10 | function () { throw new Error('__defineGetter__ ouch!'); }); 11 | log.info({obj: obj}, 'using __defineGetter__'); 12 | -------------------------------------------------------------------------------- /test/safe-json-stringify-3.js: -------------------------------------------------------------------------------- 1 | var bunyan = require('../lib/bunyan'); 2 | 3 | var log = bunyan.createLogger({ 4 | name: 'safe-json-stringify-3' 5 | }); 6 | 7 | // And using `Object.defineProperty`. 8 | var obj = {}; 9 | Object.defineProperty(obj, 'boom', { 10 | get: function () { throw new Error('defineProperty ouch!'); }, 11 | enumerable: true // enumerable is false by default 12 | }); 13 | // Twice to test the 'warnKey' usage. 14 | for (var i = 0; i < 2; i++) { 15 | log.info({obj: obj}, 'using defineProperty'); 16 | } 17 | -------------------------------------------------------------------------------- /test/safe-json-stringify-4.js: -------------------------------------------------------------------------------- 1 | process.env.BUNYAN_TEST_NO_SAFE_JSON_STRINGIFY = '1'; 2 | var bunyan = require('../lib/bunyan'); 3 | 4 | var log = bunyan.createLogger({ 5 | name: 'safe-json-stringify-4' 6 | }); 7 | 8 | // And using `Object.defineProperty`. 9 | var obj = {}; 10 | Object.defineProperty(obj, 'boom', { 11 | get: function () { throw new Error('defineProperty ouch!'); }, 12 | enumerable: true // enumerable is false by default 13 | }); 14 | // Twice to test the 'warnKey' usage. 15 | for (var i = 0; i < 2; i++) { 16 | log.info({obj: obj}, 'using defineProperty'); 17 | } 18 | -------------------------------------------------------------------------------- /test/safe-json-stringify.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Trent Mick 3 | * 4 | * If available, use `safe-json-stringfy` as a fallback stringifier. 5 | * This covers the case where an enumerable property throws an error 6 | * in its getter. 7 | * 8 | * See 9 | */ 10 | 11 | var exec = require('child_process').exec; 12 | var test = require('tap').test; 13 | 14 | test('__defineGetter__ boom', function (t) { 15 | var cmd = process.execPath + ' ' + __dirname + '/safe-json-stringify-1.js'; 16 | exec(cmd, function (err, stdout, stderr) { 17 | t.ifError(err, err); 18 | var rec = JSON.parse(stdout.trim()); 19 | t.equal(rec.obj.boom, '[Throws: __defineGetter__ ouch!]'); 20 | t.end(); 21 | }); 22 | }); 23 | 24 | test('__defineGetter__ boom, without safe-json-stringify', function (t) { 25 | var cmd = process.execPath + ' ' + __dirname + '/safe-json-stringify-2.js'; 26 | exec(cmd, function (err, stdout, stderr) { 27 | t.ifError(err, err); 28 | t.ok(stdout.indexOf('Exception in JSON.stringify') !== -1); 29 | t.ok(stderr.indexOf( 30 | 'You can install the "safe-json-stringify" module') !== -1); 31 | t.end(); 32 | }); 33 | }); 34 | 35 | test('defineProperty boom', function (t) { 36 | var cmd = process.execPath + ' ' + __dirname + '/safe-json-stringify-3.js'; 37 | exec(cmd, function (err, stdout, stderr) { 38 | t.ifError(err, err); 39 | var recs = stdout.trim().split(/\n/g); 40 | t.equal(recs.length, 2); 41 | var rec = JSON.parse(recs[0]); 42 | t.equal(rec.obj.boom, '[Throws: defineProperty ouch!]'); 43 | t.end(); 44 | }); 45 | }); 46 | 47 | test('defineProperty boom, without safe-json-stringify', function (t) { 48 | var cmd = process.execPath + ' ' + __dirname + '/safe-json-stringify-4.js'; 49 | exec(cmd, function (err, stdout, stderr) { 50 | t.ifError(err, err); 51 | t.ok(stdout.indexOf('Exception in JSON.stringify') !== -1); 52 | t.equal(stdout.match(/Exception in JSON.stringify/g).length, 2); 53 | t.ok(stderr.indexOf( 54 | 'You can install the "safe-json-stringify" module') !== -1); 55 | t.equal(stderr.match( 56 | /* JSSTYLED */ 57 | /You can install the "safe-json-stringify" module/g).length, 1); 58 | t.end(); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /test/serializers.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Trent Mick 3 | * 4 | * Test the standard serializers in Bunyan. 5 | */ 6 | 7 | var http = require('http'); 8 | var test = require('tap').test; 9 | var verror = require('verror'); 10 | 11 | var bunyan = require('../lib/bunyan'); 12 | 13 | 14 | function CapturingStream(recs) { 15 | this.recs = recs; 16 | } 17 | CapturingStream.prototype.write = function (rec) { 18 | this.recs.push(rec); 19 | } 20 | 21 | function createLogger(serializers, records) { 22 | return bunyan.createLogger({ 23 | name: 'serializer-test', 24 | streams: [ 25 | { 26 | stream: new CapturingStream(records), 27 | type: 'raw' 28 | } 29 | ], 30 | serializers: serializers 31 | }); 32 | } 33 | 34 | test('req serializer', function (t) { 35 | var records = []; 36 | var log = createLogger({ req: bunyan.stdSerializers.req }, records); 37 | 38 | // None of these should blow up. 39 | var bogusReqs = [ 40 | undefined, 41 | null, 42 | {}, 43 | 1, 44 | 'string', 45 | [1, 2, 3], 46 | {'foo':'bar'} 47 | ]; 48 | for (var i = 0; i < bogusReqs.length; i++) { 49 | log.info({req: bogusReqs[i]}, 'hi'); 50 | t.equal(records[i].req, bogusReqs[i]); 51 | } 52 | 53 | // Get http request and response objects to play with and test. 54 | var theReq, theRes; 55 | var server = http.createServer(function (req, res) { 56 | theReq = req; 57 | theRes = res; 58 | res.writeHead(200, {'Content-Type': 'text/plain'}); 59 | res.end('Hello World\n'); 60 | }) 61 | server.listen(8765, function () { 62 | http.get({host: '127.0.0.1', port: 8765, path: '/'}, function (res) { 63 | res.resume(); 64 | log.info({req: theReq}, 'the request'); 65 | var lastRecord = records[records.length-1]; 66 | t.equal(lastRecord.req.method, 'GET'); 67 | t.equal(lastRecord.req.url, theReq.url); 68 | t.equal(lastRecord.req.remoteAddress, 69 | theReq.connection.remoteAddress); 70 | t.equal(lastRecord.req.remotePort, theReq.connection.remotePort); 71 | server.close(); 72 | t.end(); 73 | }).on('error', function (err) { 74 | t.ok(false, 'error requesting to our test server: ' + err); 75 | server.close(); 76 | t.end(); 77 | }); 78 | }); 79 | }); 80 | 81 | 82 | test('req serializer - express.originalUrl', function (t) { 83 | var records = []; 84 | var log = createLogger({ req: bunyan.stdSerializers.req }, records); 85 | 86 | // Get http request and response objects to play with and test. 87 | var theReq, theRes; 88 | var server = http.createServer(function (req, res) { 89 | theReq = req; 90 | theRes = res; 91 | req.originalUrl = '/original-url' + req.url; 92 | res.writeHead(200, {'Content-Type': 'text/plain'}); 93 | res.end('Hello World\n'); 94 | }) 95 | server.listen(8765, function () { 96 | http.get({host: '127.0.0.1', port: 8765, path: '/'}, function (res) { 97 | res.resume(); 98 | log.info({req: theReq}, 'the request'); 99 | var lastRecord = records[records.length-1]; 100 | t.equal(lastRecord.req.method, 'GET'); 101 | t.equal(lastRecord.req.url, '/original-url' + theReq.url); 102 | t.equal(lastRecord.req.remoteAddress, 103 | theReq.connection.remoteAddress); 104 | t.equal(lastRecord.req.remotePort, theReq.connection.remotePort); 105 | server.close(); 106 | t.end(); 107 | }).on('error', function (err) { 108 | t.ok(false, 'error requesting to our test server: ' + err); 109 | server.close(); 110 | t.end(); 111 | }); 112 | }); 113 | }); 114 | 115 | test('res serializer', function (t) { 116 | var records = []; 117 | var log = createLogger({ res: bunyan.stdSerializers.res }, records); 118 | 119 | // None of these should blow up. 120 | var bogusRess = [ 121 | undefined, 122 | null, 123 | {}, 124 | 1, 125 | 'string', 126 | [1, 2, 3], 127 | {'foo':'bar'} 128 | ]; 129 | for (var i = 0; i < bogusRess.length; i++) { 130 | log.info({res: bogusRess[i]}, 'hi'); 131 | t.equal(records[i].res, bogusRess[i]); 132 | } 133 | 134 | // Get http request and response objects to play with and test. 135 | var theReq, theRes; 136 | var server = http.createServer(function (req, res) { 137 | theReq = req; 138 | theRes = res; 139 | res.writeHead(200, {'Content-Type': 'text/plain'}); 140 | res.end('Hello World\n'); 141 | }) 142 | server.listen(8765, function () { 143 | http.get({host: '127.0.0.1', port: 8765, path: '/'}, function (res) { 144 | res.resume(); 145 | log.info({res: theRes}, 'the response'); 146 | var lastRecord = records[records.length-1]; 147 | t.equal(lastRecord.res.statusCode, theRes.statusCode); 148 | t.equal(lastRecord.res.header, theRes._header); 149 | server.close(); 150 | t.end(); 151 | }).on('error', function (err) { 152 | t.ok(false, 'error requesting to our test server: ' + err); 153 | server.close(); 154 | t.end(); 155 | }); 156 | }); 157 | }); 158 | 159 | 160 | test('err serializer', function (t) { 161 | var records = []; 162 | var log = createLogger({ err: bunyan.stdSerializers.err }, records); 163 | 164 | // None of these should blow up. 165 | var bogusErrs = [ 166 | undefined, 167 | null, 168 | {}, 169 | 1, 170 | 'string', 171 | [1, 2, 3], 172 | {'foo':'bar'} 173 | ]; 174 | for (var i = 0; i < bogusErrs.length; i++) { 175 | log.info({err: bogusErrs[i]}, 'hi'); 176 | t.equal(records[i].err, bogusErrs[i]); 177 | } 178 | 179 | var theErr = new TypeError('blah'); 180 | 181 | log.info(theErr, 'the error'); 182 | var lastRecord = records[records.length-1]; 183 | t.equal(lastRecord.err.message, theErr.message); 184 | t.equal(lastRecord.err.name, theErr.name); 185 | t.equal(lastRecord.err.stack, theErr.stack); 186 | t.end(); 187 | }); 188 | 189 | test('err serializer: custom serializer', function (t) { 190 | var records = []; 191 | 192 | function customSerializer(err) { 193 | return { 194 | message: err.message, 195 | name: err.name, 196 | stack: err.stack, 197 | beep: err.beep 198 | }; 199 | } 200 | 201 | var log = createLogger({ err: customSerializer }, records); 202 | 203 | var e1 = new Error('message1'); 204 | e1.beep = 'bop'; 205 | var e2 = new Error('message2'); 206 | var errs = [e1, e2]; 207 | 208 | for (var i = 0; i < errs.length; i++) { 209 | log.info(errs[i]); 210 | t.equal(records[i].err.message, errs[i].message); 211 | t.equal(records[i].err.beep, errs[i].beep); 212 | } 213 | t.end(); 214 | }); 215 | 216 | test('err serializer: long stack', function (t) { 217 | var records = []; 218 | var log = createLogger({ err: bunyan.stdSerializers.err }, records); 219 | 220 | var topErr, midErr, bottomErr; 221 | 222 | // Just a VError. 223 | topErr = new verror.VError('top err'); 224 | log.info(topErr, 'the error'); 225 | var lastRecord = records[records.length-1]; 226 | t.equal(lastRecord.err.message, topErr.message, 'Just a VError'); 227 | t.equal(lastRecord.err.name, topErr.name, 'Just a VError'); 228 | t.equal(lastRecord.err.stack, topErr.stack, 'Just a VError'); 229 | 230 | // Just a WError. 231 | topErr = new verror.WError('top err'); 232 | log.info(topErr, 'the error'); 233 | var lastRecord = records[records.length-1]; 234 | t.equal(lastRecord.err.message, topErr.message, 'Just a WError'); 235 | t.equal(lastRecord.err.name, topErr.name, 'Just a WError'); 236 | t.equal(lastRecord.err.stack, topErr.stack, 'Just a WError'); 237 | 238 | // WError <- TypeError 239 | bottomErr = new TypeError('bottom err'); 240 | topErr = new verror.WError(bottomErr, 'top err'); 241 | log.info(topErr, 'the error'); 242 | var lastRecord = records[records.length-1]; 243 | t.equal(lastRecord.err.message, topErr.message, 'WError <- TypeError'); 244 | t.equal(lastRecord.err.name, topErr.name, 'WError <- TypeError'); 245 | var expectedStack = topErr.stack + '\nCaused by: ' + bottomErr.stack; 246 | t.equal(lastRecord.err.stack, expectedStack, 'WError <- TypeError'); 247 | 248 | // WError <- WError 249 | bottomErr = new verror.WError('bottom err'); 250 | topErr = new verror.WError(bottomErr, 'top err'); 251 | log.info(topErr, 'the error'); 252 | var lastRecord = records[records.length-1]; 253 | t.equal(lastRecord.err.message, topErr.message, 'WError <- WError'); 254 | t.equal(lastRecord.err.name, topErr.name, 'WError <- WError'); 255 | var expectedStack = topErr.stack + '\nCaused by: ' + bottomErr.stack; 256 | t.equal(lastRecord.err.stack, expectedStack, 'WError <- WError'); 257 | 258 | // WError <- WError <- TypeError 259 | bottomErr = new TypeError('bottom err'); 260 | midErr = new verror.WError(bottomErr, 'mid err'); 261 | topErr = new verror.WError(midErr, 'top err'); 262 | log.info(topErr, 'the error'); 263 | var lastRecord = records[records.length-1]; 264 | t.equal(lastRecord.err.message, topErr.message, 265 | 'WError <- WError <- TypeError'); 266 | t.equal(lastRecord.err.name, topErr.name, 'WError <- WError <- TypeError'); 267 | var expectedStack = (topErr.stack 268 | + '\nCaused by: ' + midErr.stack 269 | + '\nCaused by: ' + bottomErr.stack); 270 | t.equal(lastRecord.err.stack, expectedStack, 271 | 'WError <- WError <- TypeError'); 272 | 273 | // WError <- WError <- WError 274 | bottomErr = new verror.WError('bottom err'); 275 | midErr = new verror.WError(bottomErr, 'mid err'); 276 | topErr = new verror.WError(midErr, 'top err'); 277 | log.info(topErr, 'the error'); 278 | var lastRecord = records[records.length-1]; 279 | t.equal(lastRecord.err.message, topErr.message, 280 | 'WError <- WError <- WError'); 281 | t.equal(lastRecord.err.name, topErr.name, 'WError <- WError <- WError'); 282 | var expectedStack = (topErr.stack 283 | + '\nCaused by: ' + midErr.stack 284 | + '\nCaused by: ' + bottomErr.stack); 285 | t.equal(lastRecord.err.stack, expectedStack, 'WError <- WError <- WError'); 286 | 287 | t.end(); 288 | }); 289 | 290 | 291 | // Bunyan 0.18.3 introduced a bug where *all* serializers are applied 292 | // even if the log record doesn't have the associated key. That means 293 | // serializers that don't handle an `undefined` value will blow up. 294 | test('do not apply serializers if no record key', function (t) { 295 | var records = []; 296 | var log = createLogger({ 297 | err: bunyan.stdSerializers.err, 298 | boom: function (value) { 299 | throw new Error('boom'); 300 | } 301 | }, 302 | records 303 | ); 304 | 305 | log.info({foo: 'bar'}, 'record one'); 306 | log.info({err: new Error('record two err')}, 'record two'); 307 | 308 | t.equal(records[0].boom, undefined); 309 | t.equal(records[0].foo, 'bar'); 310 | t.equal(records[1].boom, undefined); 311 | t.equal(records[1].err.message, 'record two err'); 312 | 313 | t.end(); 314 | }); 315 | -------------------------------------------------------------------------------- /test/src.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Trent Mick. 3 | * 4 | * Test `src: true` usage. 5 | */ 6 | 7 | // Intentionally on line 8 for tests below: 8 | function logSomething(log) { log.info('something'); } 9 | 10 | var test = require('tap').test; 11 | var format = require('util').format; 12 | 13 | var Logger = require('../lib/bunyan'); 14 | 15 | 16 | function CapturingStream(recs) { 17 | this.recs = recs; 18 | } 19 | CapturingStream.prototype.write = function (rec) { 20 | this.recs.push(rec); 21 | } 22 | 23 | 24 | test('src', function (t) { 25 | var recs = []; 26 | 27 | var log = new Logger({ 28 | name: 'src-test', 29 | src: true, 30 | streams: [ 31 | { 32 | stream: new CapturingStream(recs), 33 | type: 'raw' 34 | } 35 | ] 36 | }); 37 | 38 | log.info('top-level'); 39 | logSomething(log); 40 | 41 | t.equal(recs.length, 2); 42 | recs.forEach(function (rec) { 43 | t.ok(rec.src); 44 | t.equal(typeof (rec.src), 'object'); 45 | t.equal(rec.src.file, __filename); 46 | t.ok(rec.src.line); 47 | t.equal(typeof (rec.src.line), 'number'); 48 | }); 49 | var rec = recs[1]; 50 | t.ok(rec.src.func); 51 | t.equal(rec.src.func, 'logSomething'); 52 | t.equal(rec.src.line, 8); 53 | 54 | t.end(); 55 | }); 56 | -------------------------------------------------------------------------------- /test/stream-levels.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Trent Mick 3 | * 4 | * Test that streams (the various way they can be added to 5 | * a Logger instance) get the appropriate level. 6 | */ 7 | 8 | var util = require('util'), 9 | format = util.format, 10 | inspect = util.inspect; 11 | var test = require('tap').test; 12 | 13 | var bunyan = require('../lib/bunyan'); 14 | 15 | 16 | // ---- Tests 17 | 18 | var log1 = bunyan.createLogger({ 19 | name: 'log1', 20 | streams: [ 21 | { 22 | path: __dirname + '/level.test.log1.log', 23 | level: 'info' 24 | } 25 | ] 26 | }); 27 | 28 | 29 | test('default stream log level', function (t) { 30 | var log = bunyan.createLogger({ 31 | name: 'foo' 32 | }); 33 | t.equal(log.level(), bunyan.INFO); 34 | t.equal(log.streams[0].level, bunyan.INFO); 35 | t.end(); 36 | }); 37 | 38 | test('default stream, level=DEBUG specified', function (t) { 39 | var log = bunyan.createLogger({ 40 | name: 'foo', 41 | level: bunyan.DEBUG 42 | }); 43 | t.equal(log.level(), bunyan.DEBUG); 44 | t.equal(log.streams[0].level, bunyan.DEBUG); 45 | t.end(); 46 | }); 47 | 48 | test('default stream, level="trace" specified', function (t) { 49 | var log = bunyan.createLogger({ 50 | name: 'foo', 51 | level: 'trace' 52 | }); 53 | t.equal(log.level(), bunyan.TRACE); 54 | t.equal(log.streams[0].level, bunyan.TRACE); 55 | t.end(); 56 | }); 57 | 58 | test('stream & level="trace" specified', function (t) { 59 | var log = bunyan.createLogger({ 60 | name: 'foo', 61 | stream: process.stderr, 62 | level: 'trace' 63 | }); 64 | t.equal(log.level(), bunyan.TRACE); 65 | t.equal(log.streams[0].level, bunyan.TRACE); 66 | t.end(); 67 | }); 68 | 69 | test('one stream, default level', function (t) { 70 | var log = bunyan.createLogger({ 71 | name: 'foo', 72 | streams: [ 73 | { 74 | stream: process.stderr 75 | } 76 | ] 77 | }); 78 | t.equal(log.level(), bunyan.INFO); 79 | t.equal(log.streams[0].level, bunyan.INFO); 80 | t.end(); 81 | }); 82 | 83 | test('one stream, top-"level" specified', function (t) { 84 | var log = bunyan.createLogger({ 85 | name: 'foo', 86 | level: 'error', 87 | streams: [ 88 | { 89 | stream: process.stderr 90 | } 91 | ] 92 | }); 93 | t.equal(log.level(), bunyan.ERROR); 94 | t.equal(log.streams[0].level, bunyan.ERROR); 95 | t.end(); 96 | }); 97 | 98 | test('one stream, stream-"level" specified', function (t) { 99 | var log = bunyan.createLogger({ 100 | name: 'foo', 101 | streams: [ 102 | { 103 | stream: process.stderr, 104 | level: 'error' 105 | } 106 | ] 107 | }); 108 | t.equal(log.level(), bunyan.ERROR); 109 | t.equal(log.streams[0].level, bunyan.ERROR); 110 | t.end(); 111 | }); 112 | 113 | test('one stream, both-"level" specified', function (t) { 114 | var log = bunyan.createLogger({ 115 | name: 'foo', 116 | level: 'debug', 117 | streams: [ 118 | { 119 | stream: process.stderr, 120 | level: 'error' 121 | } 122 | ] 123 | }); 124 | t.equal(log.level(), bunyan.ERROR); 125 | t.equal(log.streams[0].level, bunyan.ERROR); 126 | t.end(); 127 | }); 128 | 129 | test('two streams, both-"level" specified', function (t) { 130 | var log = bunyan.createLogger({ 131 | name: 'foo', 132 | level: 'debug', 133 | streams: [ 134 | { 135 | stream: process.stdout, 136 | level: 'trace' 137 | }, 138 | { 139 | stream: process.stderr, 140 | level: 'fatal' 141 | } 142 | ] 143 | }); 144 | t.equal(log.level(), bunyan.TRACE, 'log.level()'); 145 | t.equal(log.streams[0].level, bunyan.TRACE); 146 | t.equal(log.streams[1].level, bunyan.FATAL); 147 | t.end(); 148 | }); 149 | 150 | test('two streams, one with "level" specified', function (t) { 151 | var log = bunyan.createLogger({ 152 | name: 'foo', 153 | streams: [ 154 | { 155 | stream: process.stdout, 156 | }, 157 | { 158 | stream: process.stderr, 159 | level: 'fatal' 160 | } 161 | ] 162 | }); 163 | t.equal(log.level(), bunyan.INFO); 164 | t.equal(log.streams[0].level, bunyan.INFO); 165 | t.equal(log.streams[1].level, bunyan.FATAL); 166 | t.end(); 167 | }); 168 | 169 | // Issue #335 170 | test('log level 0 to turn on all logging', function (t) { 171 | var log = bunyan.createLogger({ 172 | name: 'foo', 173 | level: 0 174 | }); 175 | t.equal(log.level(), 0); 176 | t.equal(log.streams[0].level, 0); 177 | t.end(); 178 | }); 179 | -------------------------------------------------------------------------------- /tools/colors.log: -------------------------------------------------------------------------------- 1 | # A log file to use to show bunyan colors 2 | {"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} 3 | {"name":"colors","hostname":"grape.local","pid":52694,"level":20,"msg":"this is the msg","time":"2014-08-10T07:02:40.550Z","v":0} 4 | {"name":"colors","hostname":"grape.local","pid":52694,"level":30,"msg":"this is the msg","time":"2014-08-10T07:02:40.551Z","v":0} 5 | {"name":"colors","hostname":"grape.local","pid":52694,"level":40,"msg":"this is the msg","time":"2014-08-10T07:02:40.551Z","v":0} 6 | {"name":"colors","hostname":"grape.local","pid":52694,"level":50,"msg":"this is the msg","time":"2014-08-10T07:02:40.551Z","v":0} 7 | {"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} 8 | {"name":"colors","hostname":"grape.local","pid":52694,"level":60,"msg":"this is the msg","time":"2014-08-10T07:02:40.551Z","v":0} 9 | -------------------------------------------------------------------------------- /tools/cutarelease.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Copyright (c) 2009-2012 Trent Mick 4 | 5 | """cutarelease -- Cut a release of your project. 6 | 7 | A script that will help cut a release for a git-based project that follows 8 | a few conventions. It'll update your changelog (CHANGES.md), add a git 9 | tag, push those changes, update your version to the next patch level release 10 | and create a new changelog section for that new version. 11 | 12 | Conventions: 13 | - XXX 14 | """ 15 | 16 | __version_info__ = (1, 0, 7) 17 | __version__ = '.'.join(map(str, __version_info__)) 18 | 19 | import sys 20 | import os 21 | from os.path import join, dirname, normpath, abspath, exists, basename, splitext 22 | from glob import glob 23 | from pprint import pprint 24 | import re 25 | import codecs 26 | import logging 27 | import optparse 28 | import json 29 | import time 30 | 31 | 32 | 33 | #---- globals and config 34 | 35 | log = logging.getLogger("cutarelease") 36 | 37 | class Error(Exception): 38 | pass 39 | 40 | 41 | 42 | #---- main functionality 43 | 44 | def cutarelease(project_name, version_files, dry_run=False): 45 | """Cut a release. 46 | 47 | @param project_name {str} 48 | @param version_files {list} List of paths to files holding the version 49 | info for this project. 50 | 51 | If none are given it attempts to guess the version file: 52 | package.json or VERSION.txt or VERSION or $project_name.py 53 | or lib/$project_name.py or $project_name.js or lib/$project_name.js. 54 | 55 | The version file can be in one of the following forms: 56 | 57 | - A .py file, in which case the file is expect to have a top-level 58 | global called "__version_info__" as follows. [1] 59 | 60 | __version_info__ = (0, 7, 6) 61 | 62 | Note that I typically follow that with the following to get a 63 | string version attribute on my modules: 64 | 65 | __version__ = '.'.join(map(str, __version_info__)) 66 | 67 | - A .js file, in which case the file is expected to have a top-level 68 | global called "VERSION" as follows: 69 | 70 | ver VERSION = "1.2.3"; 71 | 72 | - A "package.json" file, typical of a node.js npm-using project. 73 | The package.json file must have a "version" field. 74 | 75 | - TODO: A simple version file whose only content is a "1.2.3"-style version 76 | string. 77 | 78 | [1]: This is a convention I tend to follow in my projects. 79 | Granted it might not be your cup of tea. I should add support for 80 | just `__version__ = "1.2.3"`. I'm open to other suggestions too. 81 | """ 82 | dry_run_str = dry_run and " (dry-run)" or "" 83 | 84 | if not version_files: 85 | log.info("guessing version file") 86 | candidates = [ 87 | "package.json", 88 | "VERSION.txt", 89 | "VERSION", 90 | "%s.py" % project_name, 91 | "lib/%s.py" % project_name, 92 | "%s.js" % project_name, 93 | "lib/%s.js" % project_name, 94 | ] 95 | for candidate in candidates: 96 | if exists(candidate): 97 | version_files = [candidate] 98 | break 99 | else: 100 | raise Error("could not find a version file: specify its path or " 101 | "add one of the following to your project: '%s'" 102 | % "', '".join(candidates)) 103 | log.info("using '%s' as version file", version_files[0]) 104 | 105 | parsed_version_files = [_parse_version_file(f) for f in version_files] 106 | version_file_type, version_info = parsed_version_files[0] 107 | version = _version_from_version_info(version_info) 108 | 109 | # Confirm 110 | if not dry_run: 111 | answer = query_yes_no("* * *\n" 112 | "Are you sure you want cut a %s release?\n" 113 | "This will involved commits and a push." % version, 114 | default="no") 115 | print "* * *" 116 | if answer != "yes": 117 | log.info("user abort") 118 | return 119 | log.info("cutting a %s release%s", version, dry_run_str) 120 | 121 | # Checks: Ensure there is a section in changes for this version. 122 | 123 | 124 | 125 | changes_path = "CHANGES.md" 126 | changes_txt, changes, nyr = parse_changelog(changes_path) 127 | #pprint(changes) 128 | top_ver = changes[0]["version"] 129 | if top_ver != version: 130 | raise Error("changelog '%s' top section says " 131 | "version %r, expected version %r: aborting" 132 | % (changes_path, top_ver, version)) 133 | top_verline = changes[0]["verline"] 134 | if not top_verline.endswith(nyr): 135 | answer = query_yes_no("\n* * *\n" 136 | "The changelog '%s' top section doesn't have the expected\n" 137 | "'%s' marker. Has this been released already?" 138 | % (changes_path, nyr), default="yes") 139 | print "* * *" 140 | if answer != "no": 141 | log.info("abort") 142 | return 143 | top_body = changes[0]["body"] 144 | if top_body.strip() == "(nothing yet)": 145 | raise Error("top section body is `(nothing yet)': it looks like " 146 | "nothing has been added to this release") 147 | 148 | # Commits to prepare release. 149 | changes_txt_before = changes_txt 150 | changes_txt = changes_txt.replace(" (not yet released)", "", 1) 151 | if not dry_run and changes_txt != changes_txt_before: 152 | log.info("prepare `%s' for release", changes_path) 153 | f = codecs.open(changes_path, 'w', 'utf-8') 154 | f.write(changes_txt) 155 | f.close() 156 | run('git commit %s -m "%s"' 157 | % (changes_path, version)) 158 | 159 | # Tag version and push. 160 | curr_tags = set(t for t in _capture_stdout(["git", "tag", "-l"]).split('\n') if t) 161 | if not dry_run and version not in curr_tags: 162 | log.info("tag the release") 163 | date = time.strftime("%Y-%m-%d") 164 | run('git tag -a "%s" -m "version %s (%s)"' % (version, version, date)) 165 | run('git push --tags') 166 | 167 | # Optionally release. 168 | if exists("package.json"): 169 | answer = query_yes_no("\n* * *\nPublish to npm?", default="yes") 170 | print "* * *" 171 | if answer == "yes": 172 | if dry_run: 173 | log.info("skipping npm publish (dry-run)") 174 | else: 175 | run('npm publish') 176 | elif exists("setup.py"): 177 | answer = query_yes_no("\n* * *\nPublish to pypi?", default="yes") 178 | print "* * *" 179 | if answer == "yes": 180 | if dry_run: 181 | log.info("skipping pypi publish (dry-run)") 182 | else: 183 | run("%spython setup.py sdist --formats zip upload" 184 | % _setup_command_prefix()) 185 | 186 | # Commits to prepare for future dev and push. 187 | # - update changelog file 188 | next_version_info = _get_next_version_info(version_info) 189 | next_version = _version_from_version_info(next_version_info) 190 | log.info("prepare for future dev (version %s)", next_version) 191 | marker = "## " + changes[0]["verline"] 192 | if marker.endswith(nyr): 193 | marker = marker[0:-len(nyr)] 194 | if marker not in changes_txt: 195 | raise Error("couldn't find `%s' marker in `%s' " 196 | "content: can't prep for subsequent dev" % (marker, changes_path)) 197 | next_verline = "%s %s%s" % (marker.rsplit(None, 1)[0], next_version, nyr) 198 | changes_txt = changes_txt.replace(marker + '\n', 199 | "%s\n\n(nothing yet)\n\n\n%s\n" % (next_verline, marker)) 200 | if not dry_run: 201 | f = codecs.open(changes_path, 'w', 'utf-8') 202 | f.write(changes_txt) 203 | f.close() 204 | 205 | # - update version file 206 | next_version_tuple = _tuple_from_version(next_version) 207 | for i, ver_file in enumerate(version_files): 208 | ver_content = codecs.open(ver_file, 'r', 'utf-8').read() 209 | ver_file_type, ver_info = parsed_version_files[i] 210 | if ver_file_type == "json": 211 | marker = '"version": "%s"' % version 212 | if marker not in ver_content: 213 | raise Error("couldn't find `%s' version marker in `%s' " 214 | "content: can't prep for subsequent dev" % (marker, ver_file)) 215 | ver_content = ver_content.replace(marker, 216 | '"version": "%s"' % next_version) 217 | elif ver_file_type == "javascript": 218 | candidates = [ 219 | ("single", "var VERSION = '%s';" % version), 220 | ("double", 'var VERSION = "%s";' % version), 221 | ] 222 | for quote_type, marker in candidates: 223 | if marker in ver_content: 224 | break 225 | else: 226 | raise Error("couldn't find any candidate version marker in " 227 | "`%s' content: can't prep for subsequent dev: %r" 228 | % (ver_file, candidates)) 229 | if quote_type == "single": 230 | ver_content = ver_content.replace(marker, 231 | "var VERSION = '%s';" % next_version) 232 | else: 233 | ver_content = ver_content.replace(marker, 234 | 'var VERSION = "%s";' % next_version) 235 | elif ver_file_type == "python": 236 | marker = "__version_info__ = %r" % (version_info,) 237 | if marker not in ver_content: 238 | raise Error("couldn't find `%s' version marker in `%s' " 239 | "content: can't prep for subsequent dev" % (marker, ver_file)) 240 | ver_content = ver_content.replace(marker, 241 | "__version_info__ = %r" % (next_version_tuple,)) 242 | elif ver_file_type == "version": 243 | ver_content = next_version 244 | else: 245 | raise Error("unknown ver_file_type: %r" % ver_file_type) 246 | if not dry_run: 247 | log.info("update version to '%s' in '%s'", next_version, ver_file) 248 | f = codecs.open(ver_file, 'w', 'utf-8') 249 | f.write(ver_content) 250 | f.close() 251 | 252 | if not dry_run: 253 | run('git commit %s %s -m "bumpver for subsequent work"' % ( 254 | changes_path, ' '.join(version_files))) 255 | run('git push') 256 | 257 | 258 | 259 | #---- internal support routines 260 | 261 | def _indent(s, indent=' '): 262 | return indent + indent.join(s.splitlines(True)) 263 | 264 | def _tuple_from_version(version): 265 | def _intify(s): 266 | try: 267 | return int(s) 268 | except ValueError: 269 | return s 270 | return tuple(_intify(b) for b in version.split('.')) 271 | 272 | def _get_next_version_info(version_info): 273 | next = list(version_info[:]) 274 | next[-1] += 1 275 | return tuple(next) 276 | 277 | def _version_from_version_info(version_info): 278 | v = str(version_info[0]) 279 | state_dot_join = True 280 | for i in version_info[1:]: 281 | if state_dot_join: 282 | try: 283 | int(i) 284 | except ValueError: 285 | state_dot_join = False 286 | else: 287 | pass 288 | if state_dot_join: 289 | v += "." + str(i) 290 | else: 291 | v += str(i) 292 | return v 293 | 294 | _version_re = re.compile(r"^(\d+)\.(\d+)(?:\.(\d+)([abc](\d+)?)?)?$") 295 | def _version_info_from_version(version): 296 | m = _version_re.match(version) 297 | if not m: 298 | raise Error("could not convert '%s' version to version info" % version) 299 | version_info = [] 300 | for g in m.groups(): 301 | if g is None: 302 | break 303 | try: 304 | version_info.append(int(g)) 305 | except ValueError: 306 | version_info.append(g) 307 | return tuple(version_info) 308 | 309 | def _parse_version_file(version_file): 310 | """Get version info from the given file. It can be any of: 311 | 312 | Supported version file types (i.e. types of files from which we know 313 | how to parse the version string/number -- often by some convention): 314 | - json: use the "version" key 315 | - javascript: look for a `var VERSION = "1.2.3";` or 316 | `var VERSION = '1.2.3';` 317 | - python: Python script/module with `__version_info__ = (1, 2, 3)` 318 | - version: a VERSION.txt or VERSION file where the whole contents are 319 | the version string 320 | 321 | @param version_file {str} Can be a path or "type:path", where "type" 322 | is one of the supported types. 323 | """ 324 | # Get version file *type*. 325 | version_file_type = None 326 | match = re.compile("^([a-z]+):(.*)$").search(version_file) 327 | if match: 328 | version_file = match.group(2) 329 | version_file_type = match.group(1) 330 | aliases = { 331 | "js": "javascript" 332 | } 333 | if version_file_type in aliases: 334 | version_file_type = aliases[version_file_type] 335 | 336 | f = codecs.open(version_file, 'r', 'utf-8') 337 | content = f.read() 338 | f.close() 339 | 340 | if not version_file_type: 341 | # Guess the type. 342 | base = basename(version_file) 343 | ext = splitext(base)[1] 344 | if ext == ".json": 345 | version_file_type = "json" 346 | elif ext == ".py": 347 | version_file_type = "python" 348 | elif ext == ".js": 349 | version_file_type = "javascript" 350 | elif content.startswith("#!"): 351 | shebang = content.splitlines(False)[0] 352 | shebang_bits = re.split(r'[/ \t]', shebang) 353 | for name, typ in {"python": "python", "node": "javascript"}.items(): 354 | if name in shebang_bits: 355 | version_file_type = typ 356 | break 357 | elif base in ("VERSION", "VERSION.txt"): 358 | version_file_type = "version" 359 | if not version_file_type: 360 | raise RuntimeError("can't extract version from '%s': no idea " 361 | "what type of file it it" % version_file) 362 | 363 | if version_file_type == "json": 364 | obj = json.loads(content) 365 | version_info = _version_info_from_version(obj["version"]) 366 | elif version_file_type == "python": 367 | m = re.search(r'^__version_info__ = (.*?)$', content, re.M) 368 | version_info = eval(m.group(1)) 369 | elif version_file_type == "javascript": 370 | m = re.search(r'^var VERSION = (\'|")(.*?)\1;$', content, re.M) 371 | version_info = _version_info_from_version(m.group(2)) 372 | elif version_file_type == "version": 373 | version_info = _version_info_from_version(content.strip()) 374 | else: 375 | raise RuntimeError("unexpected version_file_type: %r" 376 | % version_file_type) 377 | return version_file_type, version_info 378 | 379 | 380 | def parse_changelog(changes_path): 381 | """Parse the given changelog path and return `(content, parsed, nyr)` 382 | where `nyr` is the ' (not yet released)' marker and `parsed` looks like: 383 | 384 | [{'body': u'\n(nothing yet)\n\n', 385 | 'verline': u'restify 1.0.1 (not yet released)', 386 | 'version': u'1.0.1'}, # version is parsed out for top section only 387 | {'body': u'...', 388 | 'verline': u'1.0.0'}, 389 | {'body': u'...', 390 | 'verline': u'1.0.0-rc2'}, 391 | {'body': u'...', 392 | 'verline': u'1.0.0-rc1'}] 393 | 394 | A changelog (CHANGES.md) is expected to look like this: 395 | 396 | # $project Changelog 397 | 398 | ## $next_version (not yet released) 399 | 400 | ... 401 | 402 | ## $version1 403 | 404 | ... 405 | 406 | ## $version2 407 | 408 | ... and so on 409 | 410 | The version lines are enforced as follows: 411 | 412 | - The top entry should have a " (not yet released)" suffix. "Should" 413 | because recovery from half-cutarelease failures is supported. 414 | - A version string must be extractable from there, but it tries to 415 | be loose (though strict "X.Y.Z" versioning is preferred). Allowed 416 | 417 | ## 1.0.0 418 | ## my project 1.0.1 419 | ## foo 1.2.3-rc2 420 | 421 | Basically, (a) the " (not yet released)" is stripped, (b) the 422 | last token is the version, and (c) that version must start with 423 | a digit (sanity check). 424 | """ 425 | if not exists(changes_path): 426 | raise Error("changelog file '%s' not found" % changes_path) 427 | content = codecs.open(changes_path, 'r', 'utf-8').read() 428 | 429 | parser = re.compile( 430 | r'^##\s*(?P[^\n]*?)\s*$(?P.*?)(?=^##|\Z)', 431 | re.M | re.S) 432 | sections = parser.findall(content) 433 | 434 | # Sanity checks on changelog format. 435 | if not sections: 436 | template = "## 1.0.0 (not yet released)\n\n(nothing yet)\n" 437 | raise Error("changelog '%s' must have at least one section, " 438 | "suggestion:\n\n%s" % (changes_path, _indent(template))) 439 | first_section_verline = sections[0][0] 440 | nyr = ' (not yet released)' 441 | #if not first_section_verline.endswith(nyr): 442 | # eg = "## %s%s" % (first_section_verline, nyr) 443 | # raise Error("changelog '%s' top section must end with %r, " 444 | # "naive e.g.: '%s'" % (changes_path, nyr, eg)) 445 | 446 | items = [] 447 | for i, section in enumerate(sections): 448 | item = { 449 | "verline": section[0], 450 | "body": section[1] 451 | } 452 | if i == 0: 453 | # We only bother to pull out 'version' for the top section. 454 | verline = section[0] 455 | if verline.endswith(nyr): 456 | verline = verline[0:-len(nyr)] 457 | version = verline.split()[-1] 458 | try: 459 | int(version[0]) 460 | except ValueError: 461 | msg = '' 462 | if version.endswith(')'): 463 | msg = " (cutarelease is picky about the trailing %r " \ 464 | "on the top version line. Perhaps you misspelled " \ 465 | "that?)" % nyr 466 | raise Error("changelog '%s' top section version '%s' is " 467 | "invalid: first char isn't a number%s" 468 | % (changes_path, version, msg)) 469 | item["version"] = version 470 | items.append(item) 471 | 472 | return content, items, nyr 473 | 474 | ## {{{ http://code.activestate.com/recipes/577058/ (r2) 475 | def query_yes_no(question, default="yes"): 476 | """Ask a yes/no question via raw_input() and return their answer. 477 | 478 | "question" is a string that is presented to the user. 479 | "default" is the presumed answer if the user just hits . 480 | It must be "yes" (the default), "no" or None (meaning 481 | an answer is required of the user). 482 | 483 | The "answer" return value is one of "yes" or "no". 484 | """ 485 | valid = {"yes":"yes", "y":"yes", "ye":"yes", 486 | "no":"no", "n":"no"} 487 | if default == None: 488 | prompt = " [y/n] " 489 | elif default == "yes": 490 | prompt = " [Y/n] " 491 | elif default == "no": 492 | prompt = " [y/N] " 493 | else: 494 | raise ValueError("invalid default answer: '%s'" % default) 495 | 496 | while 1: 497 | sys.stdout.write(question + prompt) 498 | choice = raw_input().lower() 499 | if default is not None and choice == '': 500 | return default 501 | elif choice in valid.keys(): 502 | return valid[choice] 503 | else: 504 | sys.stdout.write("Please respond with 'yes' or 'no' "\ 505 | "(or 'y' or 'n').\n") 506 | ## end of http://code.activestate.com/recipes/577058/ }}} 507 | 508 | def _capture_stdout(argv): 509 | import subprocess 510 | p = subprocess.Popen(argv, stdout=subprocess.PIPE) 511 | return p.communicate()[0] 512 | 513 | class _NoReflowFormatter(optparse.IndentedHelpFormatter): 514 | """An optparse formatter that does NOT reflow the description.""" 515 | def format_description(self, description): 516 | return description or "" 517 | 518 | def run(cmd): 519 | """Run the given command. 520 | 521 | Raises OSError is the command returns a non-zero exit status. 522 | """ 523 | log.debug("running '%s'", cmd) 524 | fixed_cmd = cmd 525 | if sys.platform == "win32" and cmd.count('"') > 2: 526 | fixed_cmd = '"' + cmd + '"' 527 | retval = os.system(fixed_cmd) 528 | if hasattr(os, "WEXITSTATUS"): 529 | status = os.WEXITSTATUS(retval) 530 | else: 531 | status = retval 532 | if status: 533 | raise OSError(status, "error running '%s'" % cmd) 534 | 535 | def _setup_command_prefix(): 536 | prefix = "" 537 | if sys.platform == "darwin": 538 | # http://forums.macosxhints.com/archive/index.php/t-43243.html 539 | # This is an Apple customization to `tar` to avoid creating 540 | # '._foo' files for extended-attributes for archived files. 541 | prefix = "COPY_EXTENDED_ATTRIBUTES_DISABLE=1 " 542 | return prefix 543 | 544 | 545 | #---- mainline 546 | 547 | def main(argv): 548 | logging.basicConfig(format="%(name)s: %(levelname)s: %(message)s") 549 | log.setLevel(logging.INFO) 550 | 551 | # Parse options. 552 | parser = optparse.OptionParser(prog="cutarelease", usage='', 553 | version="%prog " + __version__, description=__doc__, 554 | formatter=_NoReflowFormatter()) 555 | parser.add_option("-v", "--verbose", dest="log_level", 556 | action="store_const", const=logging.DEBUG, 557 | help="more verbose output") 558 | parser.add_option("-q", "--quiet", dest="log_level", 559 | action="store_const", const=logging.WARNING, 560 | help="quieter output (just warnings and errors)") 561 | parser.set_default("log_level", logging.INFO) 562 | parser.add_option("--test", action="store_true", 563 | help="run self-test and exit (use 'eol.py -v --test' for verbose test output)") 564 | parser.add_option("-p", "--project-name", metavar="NAME", 565 | help='the name of this project (default is the base dir name)', 566 | default=basename(os.getcwd())) 567 | parser.add_option("-f", "--version-file", metavar="[TYPE:]PATH", 568 | action='append', dest="version_files", 569 | help='The path to the project file holding the version info. Can be ' 570 | 'specified multiple times if more than one file should be updated ' 571 | 'with new version info. If excluded, it will be guessed.') 572 | parser.add_option("-n", "--dry-run", action="store_true", 573 | help='Do a dry-run', default=False) 574 | opts, args = parser.parse_args() 575 | log.setLevel(opts.log_level) 576 | 577 | cutarelease(opts.project_name, opts.version_files, dry_run=opts.dry_run) 578 | 579 | 580 | ## {{{ http://code.activestate.com/recipes/577258/ (r5+) 581 | if __name__ == "__main__": 582 | try: 583 | retval = main(sys.argv) 584 | except KeyboardInterrupt: 585 | sys.exit(1) 586 | except SystemExit: 587 | raise 588 | except: 589 | import traceback, logging 590 | if not log.handlers and not logging.root.handlers: 591 | logging.basicConfig() 592 | skip_it = False 593 | exc_info = sys.exc_info() 594 | if hasattr(exc_info[0], "__name__"): 595 | exc_class, exc, tb = exc_info 596 | if isinstance(exc, IOError) and exc.args[0] == 32: 597 | # Skip 'IOError: [Errno 32] Broken pipe': often a cancelling of `less`. 598 | skip_it = True 599 | if not skip_it: 600 | tb_path, tb_lineno, tb_func = traceback.extract_tb(tb)[-1][:3] 601 | log.error("%s (%s:%s in %s)", exc_info[1], tb_path, 602 | tb_lineno, tb_func) 603 | else: # string exception 604 | log.error(exc_info[0]) 605 | if not skip_it: 606 | if log.isEnabledFor(logging.DEBUG): 607 | traceback.print_exception(*exc_info) 608 | sys.exit(1) 609 | else: 610 | sys.exit(retval) 611 | ## end of http://code.activestate.com/recipes/577258/ }}} 612 | -------------------------------------------------------------------------------- /tools/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trentm/node-bunyan/5c2258ecb1d33ba34bd7fbd6167e33023dc06e40/tools/screenshot1.png -------------------------------------------------------------------------------- /tools/statsd-notes.txt: -------------------------------------------------------------------------------- 1 | 2 | # building pycairo (needed for graphite) 3 | 4 | wget http://cairographics.org/releases/pycairo-1.10.0.tar.bz2 5 | 6 | hack pycairo wscript: 7 | #ctx.check_python_version((3,1,0)) 8 | 9 | brew install cairo 10 | LDFLAGS -L/usr/local/Cellar/cairo/1.10.2/lib 11 | CPPFLAGS -I/usr/local/Cellar/cairo/1.10.2/include 12 | 13 | PKG_CONFIG_PATH=/usr/local/Cellar/cairo/1.10.2/lib/pkgconfig ./waf configure 14 | 15 | FAIL so far. 16 | -------------------------------------------------------------------------------- /tools/timechild.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | * Time `log.child(...)`. 4 | * 5 | * Getting 0.011ms on my Mac. For about 1000 req/s that means that the 6 | * `log.child` would be about 1% of the time handling that request. 7 | * Could do better. I.e. consider a hackish fast path. 8 | * 9 | * ... 10 | * 11 | * Added: `log.fastchild({...}, true)`. Use the `true` to assert that 12 | * the given options are just new fields (and no serializers). 13 | * Result: Another order of magnitude. 14 | */ 15 | 16 | var ben = require('ben'); // npm install ben 17 | var Logger = require('../lib/bunyan'); 18 | 19 | var log = new Logger({ 20 | name: 'svc', 21 | streams: [ 22 | { 23 | path: __dirname + '/timechild.log' 24 | }, 25 | { 26 | stream: process.stdout 27 | } 28 | ], 29 | serializers: { 30 | err: Logger.stdSerializers.err 31 | } 32 | }); 33 | 34 | console.log('Time `log.child`:'); 35 | 36 | var ms = ben(1e5, function () { 37 | var child = log.child(); 38 | }); 39 | console.log(' - adding no fields: %dms per iteration', ms); 40 | 41 | var ms = ben(1e5, function () { 42 | var child = log.child({a:1}); 43 | }); 44 | console.log(' - adding one field: %dms per iteration', ms); 45 | 46 | var ms = ben(1e5, function () { 47 | var child = log.child({a:1, b:2}); 48 | }); 49 | console.log(' - adding two fields: %dms per iteration', ms); 50 | 51 | function fooSerializer(obj) { 52 | return {bar: obj.bar}; 53 | } 54 | var ms = ben(1e5, function () { 55 | var child = log.child({ 56 | a: 1, 57 | serializers: {foo: fooSerializer} 58 | }); 59 | }); 60 | console.log(' - adding serializer and one field: %dms per iteration', ms); 61 | 62 | var ms = ben(1e5, function () { 63 | var child = log.child({ 64 | a: 1, 65 | streams: [ {stream: process.stderr} ] 66 | }); 67 | }); 68 | console.log(' - adding a (stderr) stream and one field: %dms per iteration', 69 | ms); 70 | 71 | var ms = ben(1e6, function () { 72 | var child = log.child({}, true); 73 | }); 74 | console.log(' - [fast] adding no fields: %dms per iteration', ms); 75 | 76 | var ms = ben(1e6, function () { 77 | var child = log.child({a:1}, true); 78 | }); 79 | console.log(' - [fast] adding one field: %dms per iteration', ms); 80 | 81 | var ms = ben(1e6, function () { 82 | var child = log.child({a:1, b:2}, true); 83 | }); 84 | console.log(' - [fast] adding two fields: %dms per iteration', ms); 85 | -------------------------------------------------------------------------------- /tools/timeguard.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | * Time logging with/without a try/catch-guard on the JSON.stringify 4 | * and other code options around that section (see #427). 5 | */ 6 | 7 | console.log('Time JSON.stringify and alternatives in Logger._emit:'); 8 | 9 | var ben = require('ben'); // npm install ben 10 | var bunyan = require('../lib/bunyan'); 11 | 12 | function Collector() {} 13 | Collector.prototype.write = function (s) {}; 14 | 15 | var log = bunyan.createLogger({ 16 | name: 'timeguard', 17 | stream: new Collector() 18 | }); 19 | 20 | var ms, fields; 21 | ms = ben(1e5, function () { log.info('hi'); }); 22 | console.log(' - log.info with no fields: %dms per iteration', ms); 23 | 24 | fields = {foo: 'bar'}; 25 | ms = ben(1e5, function () { log.info(fields, 'hi'); }); 26 | console.log(' - log.info with small fields: %dms per iteration', ms); 27 | 28 | fields = { 29 | versions: process.versions, 30 | moduleLoadList: process.moduleLoadList 31 | }; 32 | ms = ben(1e5, function () { log.info(fields, 'hi'); }); 33 | console.log(' - log.info with medium fields: %dms per iteration', ms); 34 | 35 | // JSSTYLED 36 | 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}; 37 | ms = ben(1e5, function () { log.info(fields, 'hi'); }); 38 | console.log(' - log.info with large fields: %dms per iteration', ms); 39 | 40 | console.log('\nNow you need to manually change `Logger.prototype._emit` in' 41 | + '"../lib/bunyan.js"\nto an alternative impl. Then re-run this a ' 42 | + 'few times to compare speed.'); 43 | -------------------------------------------------------------------------------- /tools/timenop.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | * Time logging below the current level, which should do very little work. 4 | */ 5 | 6 | console.log('Time log.trace() when log level is "info":'); 7 | 8 | var ben = require('ben'); // npm install ben 9 | var bunyan = require('../lib/bunyan'); 10 | 11 | function Collector() {} 12 | Collector.prototype.write = function (s) {}; 13 | 14 | var log = bunyan.createLogger({ 15 | name: 'timeguard', 16 | level: 'info', 17 | stream: new Collector() 18 | }); 19 | 20 | var i = 0; 21 | var ms, fields; 22 | 23 | ms = ben(1e7, function () { 24 | log.trace({ count: i++ }, 'hello'); 25 | }); 26 | console.log(' - log.trace: %dms per iteration', ms); 27 | -------------------------------------------------------------------------------- /tools/timesrc.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | * Time 'src' fields (getting log call source info). This is expensive. 4 | */ 5 | 6 | console.log('Time adding "src" field with call source info:'); 7 | 8 | var ben = require('ben'); // npm install ben 9 | var Logger = require('../lib/bunyan'); 10 | 11 | var records = []; 12 | function Collector() { 13 | } 14 | Collector.prototype.write = function (s) { 15 | //records.push(s); 16 | } 17 | var collector = new Collector(); 18 | 19 | var logwith = new Logger({ 20 | name: 'with-src', 21 | src: true, 22 | stream: collector 23 | }); 24 | 25 | var ms = ben(1e5, function () { 26 | logwith.info('hi'); 27 | }); 28 | console.log(' - log.info with src: %dms per iteration', ms); 29 | 30 | var logwithout = new Logger({ 31 | name: 'without-src', 32 | stream: collector 33 | }); 34 | var ms = ben(1e5, function () { 35 | logwithout.info('hi'); 36 | }); 37 | console.log(' - log.info without src: %dms per iteration', ms); 38 | --------------------------------------------------------------------------------