{"id":24291482,"url":"https://github.com/robhaswell/canopi-node","last_synced_at":"2025-03-06T03:38:22.309Z","repository":{"id":16671888,"uuid":"80345721","full_name":"robhaswell/canopi-node","owner":"robhaswell","description":"🌴 A simple Node logging library for the cloud","archived":false,"fork":false,"pushed_at":"2023-01-11T07:55:00.000Z","size":284,"stargazers_count":0,"open_issues_count":8,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-02-16T18:16:52.918Z","etag":null,"topics":["bole","cloud","error-handler","json","logger","logging","logging-library","node","node-js","node-module","nodejs"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/robhaswell.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-01-29T12:48:44.000Z","updated_at":"2019-11-12T09:55:20.000Z","dependencies_parsed_at":"2023-01-13T18:58:01.421Z","dependency_job_id":null,"html_url":"https://github.com/robhaswell/canopi-node","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robhaswell%2Fcanopi-node","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robhaswell%2Fcanopi-node/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robhaswell%2Fcanopi-node/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robhaswell%2Fcanopi-node/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/robhaswell","download_url":"https://codeload.github.com/robhaswell/canopi-node/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242144498,"owners_count":20078964,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bole","cloud","error-handler","json","logger","logging","logging-library","node","node-js","node-module","nodejs"],"created_at":"2025-01-16T14:33:21.851Z","updated_at":"2025-03-06T03:38:22.277Z","avatar_url":"https://github.com/robhaswell.png","language":"JavaScript","readme":"# Canopi [ ![Codeship Status for robhaswell/canopi](https://app.codeship.com/projects/49893e50-c891-0134-6fb0-6ac8e955f005/status?branch=master)](https://app.codeship.com/projects/199046)\n🌴 A simple Node logging library for the cloud\n\n```js\nconst log = canopi('app')\nlog.setOutputStream(process.stdout)\n\nlog.info('something happened', { thing: value })\n// {\"timestamp\":\"...\",\"name\":\"app\",\"message\":\"something happened\",\"thing\":value}\n\nconst eventLog = log('events')\nlog.info({ kind: 'createWidget', widget: widget })\n// {\"timestamp\":\"...\",\"name\":\"app:events\",\"kind\":\"createWidget\",\"widget\":widget}\n\nconst widgetLog = eventLog({ widget: widget })\nlog.error(new Error(), { kind: 'widgetFailed' })\n// {\"timestamp\":\"...\",\"name\":\"app:events\",\"kind\":\"widgetFailed\",\"widget\":widget,\"err\":{ name, message, [code], stack }}\n\ncanopi.addErrorHandler((err) =\u003e { errors.report(err) })\n// Call `errors.report` with any logged `log.\u003clevel\u003e(err)`\n```\n\n## Motivation\n\nCanopi was created to support the logging needs of a microservices application deployed to Google Container Engine.\nIn order to effectively debug a distributed application, it is required to be able to trace logs which relate to a given request, event or entity.\nThis means that every log message needs to have some sort of context to be useful, usually supplied an object of relevant properties and values.\nAdditionally, there is no requirement for alternative logging streams or level-based filtering, as logs are processed by log collection tooling.\n\nOther logging solutions such as [Bole](https://github.com/rvagg/canopi) or [Bunyan](https://github.com/trentm/node-bunyan) make it difficult to log errors with some context, or are overly burdened with unnecessary functionality, so we created Canopi.\n\nThe API for Canopi is heavily inspired by Bole.\n\n## API\n\n### canopi('name')\n\nCreate a new **logger** with the supplied `name` to be attached to each output.\nIf you keep a logger-per module you don't need to pass loggers around, *keep your concerns separated*.\n\n### canopi.setOutputStream(process.stdout)\n\nConfigure the output stream.\n\n### canopi({ aProperty: 'aValue'})\n\nCreate a new **logger** with the fields from the supplied object included in the output.\n\n### canopi('name', { aProperty: 'aValue'})\n\nCreate a new **logger** with the supplied `name` and fields to be included in the output.\n\n### logger#debug(), logger#info(), logger#warn(), logger#error()\n\nLoggers have 4 roughly identical log methods, one for each of the supports log-levels.\nLog levels are recorded on the output and can be used to determine the level of detail passed to the output.\n\nLog methods support the following types of input:\n\n#### logger.\\\u003clevel\\\u003e('message')\n\nProviding a message string will output the message under the property `message`.\nNo substitution is performed.\n\n#### logger.\\\u003clevel\\\u003e(\"message\", { an: 'object' })\n\nIf an object is provided with a message, the behaviour is as above but with additional properties from the object included in the output.\n\n#### logger.\\\u003clevel\\\u003e({ an: 'object' })\n\nProviding an object will log all the fields of the object.\n\n#### logger.\\\u003clevel\\\u003e(err)\n\n**`Error` objects**: log output will include the error `name`, `message`, complete `stack` and also a `code` where there is one.\n\n#### logger.\\\u003clevel\\\u003e(err, { an: 'object' })\n\nThe error will be logged as above, but with additional properties from the object included in the output.\n\n#### logger.\\\u003clevel\\\u003e(err, 'message')\n\nThe error will be logged as above, but with the additional `message` property.\n\n**Note:** Any other call signature will log a `CanopiUsageError` at the requested log level.\n\n### logger()\n\nThe `logger` object returned by `canopi()` is also a function that accepts a `name` or `object` argument.\nIt returns a new logger whose name is the parent logger with the new name appended after a `':'` character.\nThis is useful for splitting a logger up for grouping events.\nConsider the HTTP server case where you may want to group all events from a particular request together:\n\n```js\nvar log = canopi('server')\n\nhttp.createServer(function (req, res) {\n  req.log = log(uuid.v4()) // make a new sub-logger\n  req.log.info(req)\n\n  //...\n\n  // log an error against this sub-logger\n  req.log.error(err)\n})\n```\n\n## Formatters\n\nCanopi supports _formatters_, which are convenience functions the user can defined which format unweildy objects.\nA formatting function takes two arguments, a _key_ and a function.\nThe function will be called with the value of the _key_ if anything is logged under that _key_.\n\n```js\ncanopi.setFormatter('upper', (value) =\u003e value.toUpperCase())\nlog.info({ upper: 'make me uppercase' })\n// {\"timestamp\":\"...\",\"upper\":\"MAKE ME UPPERCASE\"}\n\ncanopi.setFormatter('upper', null)\nlog.info({ upper: 'make me uppercase' })\n// {\"timestamp\":\"...\",\"upper\":\"make me uppercase\"}\n```\n\nA default formatter for `request` objects is available as `canopi.requestFormatter`.\nThis can also be set (along with others in the future) with `canopi.defaultFormatters()`.\n\nFormatters are not chainable .\n\n## Error handlers\n\nCanopi supports a list of error handlers which are called in turn with each error logged.\nErrors are passed to error handlers only if they are the first argument to a log method, they are an instance of `Error`, and the log level is `error`.\n\n#### canopi.addErrorHandler(function callback(err) { ... })\n\nCall `callback` with a single argument of the error instance whenever an error is logged as the first argument to a log method.\n\n## Other methods\n\n### canopi.quiet()\n\nSilence the logger output by preventing it from writing to `process.stdout` and removing any error handlers.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobhaswell%2Fcanopi-node","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frobhaswell%2Fcanopi-node","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobhaswell%2Fcanopi-node/lists"}