diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 9831883b51c..5a0779bc5fe 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,4 +1,5 @@ { + "handwritten/nodejs-logging-bunyan": "5.1.0", "packages/gapic-node-processing": "0.1.6", "packages/google-ads-admanager": "0.5.0", "packages/google-ads-datamanager": "0.1.0", @@ -145,11 +146,11 @@ "packages/google-cloud-saasplatform-saasservicemgmt": "0.1.1", "packages/google-cloud-scheduler": "5.3.1", "packages/google-cloud-secretmanager": "6.1.1", + "packages/google-cloud-securesourcemanager": "0.8.1", "packages/google-cloud-security-privateca": "7.0.1", "packages/google-cloud-security-publicca": "2.2.1", "packages/google-cloud-securitycenter": "9.2.1", "packages/google-cloud-securitycentermanagement": "0.7.1", - "packages/google-cloud-securesourcemanager": "0.8.1", "packages/google-cloud-servicedirectory": "6.1.1", "packages/google-cloud-servicehealth": "0.7.1", "packages/google-cloud-shell": "4.1.1", @@ -217,4 +218,4 @@ "packages/google-streetview-publish": "0.4.1", "packages/grafeas": "6.1.1", "packages/typeless-sample-bot": "3.1.1" -} \ No newline at end of file +} diff --git a/handwritten/nodejs-logging-bunyan/.OwlBot.yaml b/handwritten/nodejs-logging-bunyan/.OwlBot.yaml new file mode 100644 index 00000000000..10389756341 --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/.OwlBot.yaml @@ -0,0 +1,17 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +begin-after-commit-hash: 674a41e0de2869f44f45eb7b1a605852a5394bba + diff --git a/handwritten/nodejs-logging-bunyan/.eslintignore b/handwritten/nodejs-logging-bunyan/.eslintignore new file mode 100644 index 00000000000..c4a0963e9bd --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/.eslintignore @@ -0,0 +1,8 @@ +**/node_modules +**/coverage +test/fixtures +build/ +docs/ +protos/ +samples/generated/ +system-test/**/fixtures diff --git a/handwritten/nodejs-logging-bunyan/.eslintrc.json b/handwritten/nodejs-logging-bunyan/.eslintrc.json new file mode 100644 index 00000000000..78215349546 --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "./node_modules/gts" +} diff --git a/handwritten/nodejs-logging-bunyan/.gitattributes b/handwritten/nodejs-logging-bunyan/.gitattributes new file mode 100644 index 00000000000..33739cb74e4 --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/.gitattributes @@ -0,0 +1,4 @@ +*.ts text eol=lf +*.js text eol=lf +protos/* linguist-generated +**/api-extractor.json linguist-language=JSON-with-Comments diff --git a/handwritten/nodejs-logging-bunyan/.gitignore b/handwritten/nodejs-logging-bunyan/.gitignore new file mode 100644 index 00000000000..5b1296e1703 --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/.gitignore @@ -0,0 +1,16 @@ +**/*.log +**/node_modules +.coverage +.nyc_output +docs/ +out/ +build/ +system-test/secrets.js +system-test/*key.json +*.lock +.DS_Store +google-cloud-logging-winston-*.tgz +google-cloud-logging-bunyan-*.tgz +package-lock.json +key.json +__pycache__ diff --git a/handwritten/nodejs-logging-bunyan/.jsdoc.js b/handwritten/nodejs-logging-bunyan/.jsdoc.js new file mode 100644 index 00000000000..bf83cc4b172 --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/.jsdoc.js @@ -0,0 +1,51 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +'use strict'; + +module.exports = { + opts: { + readme: './README.md', + package: './package.json', + template: './node_modules/jsdoc-fresh', + recurse: true, + verbose: true, + destination: './docs/' + }, + plugins: [ + 'plugins/markdown', + 'jsdoc-region-tag' + ], + source: { + excludePattern: '(^|\\/|\\\\)[._]', + include: [ + 'build/src' + ], + includePattern: '\\.js$' + }, + templates: { + copyright: 'Copyright 2019 Google, LLC.', + includeDate: false, + sourceFiles: false, + systemName: '@google-cloud/logging-bunyan', + theme: 'lumen', + default: { + "outputSourceFiles": false + } + }, + markdown: { + idInHeadings: true + } +}; diff --git a/handwritten/nodejs-logging-bunyan/.mocharc.js b/handwritten/nodejs-logging-bunyan/.mocharc.js new file mode 100644 index 00000000000..0b600509bed --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/.mocharc.js @@ -0,0 +1,29 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +const config = { + "enable-source-maps": true, + "throw-deprecation": true, + "timeout": 10000, + "recursive": true +} +if (process.env.MOCHA_THROW_DEPRECATION === 'false') { + delete config['throw-deprecation']; +} +if (process.env.MOCHA_REPORTER) { + config.reporter = process.env.MOCHA_REPORTER; +} +if (process.env.MOCHA_REPORTER_OUTPUT) { + config['reporter-option'] = `output=${process.env.MOCHA_REPORTER_OUTPUT}`; +} +module.exports = config diff --git a/handwritten/nodejs-logging-bunyan/.nycrc b/handwritten/nodejs-logging-bunyan/.nycrc new file mode 100644 index 00000000000..b18d5472b62 --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/.nycrc @@ -0,0 +1,24 @@ +{ + "report-dir": "./.coverage", + "reporter": ["text", "lcov"], + "exclude": [ + "**/*-test", + "**/.coverage", + "**/apis", + "**/benchmark", + "**/conformance", + "**/docs", + "**/samples", + "**/scripts", + "**/protos", + "**/test", + "**/*.d.ts", + ".jsdoc.js", + "**/.jsdoc.js", + "karma.conf.js", + "webpack-tests.config.js", + "webpack.config.js" + ], + "exclude-after-remap": false, + "all": true +} diff --git a/handwritten/nodejs-logging-bunyan/.prettierignore b/handwritten/nodejs-logging-bunyan/.prettierignore new file mode 100644 index 00000000000..9340ad9b86d --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/.prettierignore @@ -0,0 +1,6 @@ +**/node_modules +**/coverage +test/fixtures +build/ +docs/ +protos/ diff --git a/handwritten/nodejs-logging-bunyan/.prettierrc.js b/handwritten/nodejs-logging-bunyan/.prettierrc.js new file mode 100644 index 00000000000..d1b95106f4c --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/.prettierrc.js @@ -0,0 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +module.exports = { + ...require('gts/.prettierrc.json') +} diff --git a/handwritten/nodejs-logging-bunyan/.readme-partials.yml b/handwritten/nodejs-logging-bunyan/.readme-partials.yml new file mode 100644 index 00000000000..38bc9f9b622 --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/.readme-partials.yml @@ -0,0 +1,186 @@ +introduction: |- + This module provides an easy to use, higher-level layer for working with [Cloud Logging](https://cloud.google.com/logging/docs), + compatible with [Bunyan](https://www.npmjs.com/package/bunyan). Simply attach this as a transport to your existing Bunyan loggers. +body: |- + ### Using as an express middleware + + ***NOTE: this feature is experimental. The API may change in a backwards + incompatible way until this is deemed stable. Please provide us feedback so + that we can better refine this express integration.*** + + We provide a middleware that can be used in an express application. Apart from + being easy to use, this enables some more powerful features of Cloud + Logging: request bundling. Any application logs emitted on behalf of a specific + request will be shown nested inside the request log as you see in this + screenshot: + + ![Request Bundling Example](https://raw.githubusercontent.com/googleapis/nodejs-logging-bunyan/master/doc/images/request-bundling.png) + + The middleware adds a `bunyan`-style log function to the `request` object. You + can use this wherever you have access to the `request` object (`req` in the + sample below). All log entries that are made on behalf of a specific request are + shown bundled together in the Cloud Logging UI. + + ```javascript + const lb = require('@google-cloud/logging-bunyan'); + + // Import express module and create an http server. + const express = require('express'); + + async function startServer() { + const {logger, mw} = await lb.express.middleware(); + const app = express(); + + // Install the logging middleware. This ensures that a Bunyan-style `log` + // function is available on the `request` object. Attach this as one of the + // earliest middleware to make sure that log function is available in all the + // subsequent middleware and routes. + app.use(mw); + + // Setup an http route and a route handler. + app.get('/', (req, res) => { + // `req.log` can be used as a bunyan style log method. All logs generated + // using `req.log` use the current request context. That is, all logs + // corresponding to a specific request will be bundled in the Cloud UI. + req.log.info('this is an info log message'); + res.send('hello world'); + }); + + // `logger` can be used as a global logger, one not correlated to any specific + // request. + logger.info({port: 8080}, 'bonjour'); + + // Start listening on the http server. + app.listen(8080, () => { + console.log('http server listening on port 8080'); + }); + } + + startServer(); + ``` + + ### Error Reporting + + Any `Error` objects you log at severity `error` or higher can automatically be picked up by [Cloud Error Reporting](https://cloud.google.com/error-reporting/) if you have specified a `serviceContext.service` when instantiating a `LoggingBunyan` instance: + + ```javascript + const loggingBunyan = new LoggingBunyan({ + serviceContext: { + service: 'my-service', // required to report logged errors + // to the Google Cloud Error Reporting + // console + version: 'my-version' + } + }); + ``` + + It is an error to specify a `serviceContext` but not specify `serviceContext.service`. + + Make sure to add logs to your [uncaught exception](https://nodejs.org/api/process.html#process_event_uncaughtexception) and [unhandled rejection](https://nodejs.org/api/process.html#process_event_unhandledrejection) handlers if you want to see those errors too. + + You may also want to see the [@google-cloud/error-reporting][@google-cloud/error-reporting] module which provides direct access to the Error Reporting API. + + ### Special Payload Fields in LogEntry + + There are some fields that are considered special by Google cloud logging and will be extracted into the LogEntry structure. For example, `severity`, `message` and `labels` can be extracted to LogEntry if included in the bunyan log payload. These [special JSON fields](https://cloud.google.com/logging/docs/structured-logging#special-payload-fields) will be used to set the corresponding fields in the `LogEntry`. Please be aware of these special fields to avoid unexpected logging behavior. + + ### LogEntry Labels + + If the bunyan log record contains a label property where all the values are strings, we automatically promote that + property to be the [`LogEntry.labels`](https://cloud.google.com/logging/docs/reference/v2/rpc/google.logging.v2#logentry) value rather + than being one of the properties in the `payload` fields. This makes it easier to filter the logs in the UI using the labels. + + ```javascript + logger.info({labels: {someKey: 'some value'}}, 'test log message'); + ``` + + All the label values must be strings for this automatic promotion to work. Otherwise the labels are left in the payload. + + ### Formatting Request Logs + + To format your request logs you can provide a `httpRequest` property on the bunyan metadata you provide along with the log message. We will treat this as the [`HttpRequest`](https://cloud.google.com/logging/docs/reference/v2/rpc/google.logging.type#google.logging.type.HttpRequest) message and Cloud logging will show this as a request log. Example: + + ![Request Log Example](https://raw.githubusercontent.com/googleapis/nodejs-logging-bunyan/master/doc/images/request-log.png) + + ```js + logger.info({ + httpRequest: { + status: res.statusCode, + requestUrl: req.url, + requestMethod: req.method, + remoteIp: req.connection.remoteAddress, + // etc. + } + }, req.path); + ``` + + The `httpRequest` property must be a properly formatted [`HttpRequest`](https://cloud.google.com/logging/docs/reference/v2/rpc/google.logging.type#google.logging.type.HttpRequest) message. (Note: the linked protobuf documentation shows `snake_case` property names, but in JavaScript one needs to provide property names in `camelCase`.) + + ### Correlating Logs with Traces + + If you use [@google-cloud/trace-agent][trace-agent] module, then this module will set the Cloud Logging [LogEntry][LogEntry] `trace` property based on the current trace context when available. That correlation allows you to [view log entries][trace-viewing-log-entries] inline with trace spans in the Cloud Trace Viewer. Example: + + ![Logs in Trace Example](https://raw.githubusercontent.com/googleapis/nodejs-logging-bunyan/master/doc/images/bunyan-logs-in-trace.png) + + If you wish to set the Cloud LogEntry `trace` property with a custom value, then write a Bunyan log entry property for `'logging.googleapis.com/trace'`, which is exported by this module as `LOGGING_TRACE_KEY`. For example: + + ```js + const bunyan = require('bunyan'); + // Node 6+ + const {LoggingBunyan, LOGGING_TRACE_KEY} = require('@google-cloud/logging-bunyan'); + const loggingBunyan = LoggingBunyan(); + + ... + + logger.info({ + [LOGGING_TRACE_KEY]: 'custom-trace-value' + }, 'Bunyan log entry with custom trace field'); + ``` + + ### Error handling with a default callback + The `LoggingBunyan` class creates an instance of `Logging` which creates the `Log` class from `@google-cloud/logging` package to write log entries. + The `Log` class writes logs asynchronously and there are cases when log entries cannot be written when it fails or an error is returned from Logging backend. + If the error is not handled, it could crash the application. One possible way to handle the error is to provide a default callback + to the `LoggingBunyan` constructor which will be used to initialize the `Log` object with that callback like in the example below: + + ```js + // Imports the Google Cloud client library for Bunyan + const {LoggingBunyan} = require('@google-cloud/logging-bunyan'); + // Creates a client + const loggingBunyan = new LoggingBunyan({ + projectId: 'your-project-id', + keyFilename: '/path/to/key.json', + defaultCallback: err => { + if (err) { + console.log('Error occured: ' + err); + } + }, + }); + ``` + + ### Alternative way to ingest logs in Google Cloud managed environments + If you use this library with the Cloud Logging Agent, you can configure the handler to output logs to `process.stdout` using + the [structured logging Json format](https://cloud.google.com/logging/docs/structured-logging#special-payload-fields). + To do this, add `redirectToStdout: true` parameter to the `LoggingBunyan` constructor as in sample below. + You can use this parameter when running applications in Google Cloud managed environments such as AppEngine, Cloud Run, + Cloud Function or GKE. The logger agent installed on these environments can capture `process.stdout` and ingest it into Cloud Logging. + The agent can parse structured logs printed to `process.stdout` and capture additional log metadata beside the log payload. + It is recommended to set `redirectToStdout: true` in serverless environments like Cloud Functions since it could + decrease logging record loss upon execution termination - since all logs are written to `process.stdout` those + would be picked up by the Cloud Logging Agent running in Google Cloud managed environment. + Note that there is also a `useMessageField` option which controls if "message" field is used to store + structured, non-text data inside `jsonPayload` field when `redirectToStdout` is set. By default `useMessageField` is always `true`. + Set the `skipParentEntryForCloudRun` option to skip creating an entry for the request itself as Cloud Run already automatically creates + such log entries. This might become the default behaviour in a next major version. + + ```js + // Imports the Google Cloud client library for Bunyan + const {LoggingBunyan} = require('@google-cloud/logging-bunyan'); + + // Creates a client + const loggingBunyan = new LoggingBunyan({ + projectId: 'your-project-id', + keyFilename: '/path/to/key.json', + redirectToStdout: true, + }); + ``` diff --git a/handwritten/nodejs-logging-bunyan/.repo-metadata.json b/handwritten/nodejs-logging-bunyan/.repo-metadata.json new file mode 100644 index 00000000000..fe857deb569 --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/.repo-metadata.json @@ -0,0 +1,14 @@ +{ + "name": "logging-bunyan", + "name_pretty": "Cloud Logging for Bunyan", + "product_documentation": "https://cloud.google.com/logging", + "client_documentation": "https://cloud.google.com/nodejs/docs/reference/logging-bunyan/latest", + "issue_tracker": "https://issuetracker.google.com/savedsearches/559764", + "release_level": "stable", + "language": "nodejs", + "repo": "googleapis/google-cloud-node", + "distribution_name": "@google-cloud/logging-bunyan", + "api_id": "logging.googleapis.com", + "codeowner_team": "@googleapis/yoshi-nodejs", + "library_type": "OTHER" +} diff --git a/handwritten/nodejs-logging-bunyan/CHANGELOG.md b/handwritten/nodejs-logging-bunyan/CHANGELOG.md new file mode 100644 index 00000000000..39fdfd562ac --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/CHANGELOG.md @@ -0,0 +1,479 @@ +# Changelog + +[npm history][1] + +[1]: https://www.npmjs.com/package/nodejs-logging-bunyan?activeTab=versions + +## [5.1.0](https://github.com/googleapis/nodejs-logging-bunyan/compare/v5.0.1...v5.1.0) (2024-01-31) + + +### Features + +* Enable custom json fields truncation for bunyan ([#732](https://github.com/googleapis/nodejs-logging-bunyan/issues/732)) ([eb6fa66](https://github.com/googleapis/nodejs-logging-bunyan/commit/eb6fa6615cd43f192c07f69a5329a5d4ba159206)) + + +### Bug Fixes + +* Add speciel logging fields section in the doc ([#728](https://github.com/googleapis/nodejs-logging-bunyan/issues/728)) ([0b9ad2b](https://github.com/googleapis/nodejs-logging-bunyan/commit/0b9ad2b039ea282caf42284512aa61aaf2ed89e1)) +* Fix rest links with the correct gRPC links used by library. ([#724](https://github.com/googleapis/nodejs-logging-bunyan/issues/724)) ([555c33e](https://github.com/googleapis/nodejs-logging-bunyan/commit/555c33e858248b911a5aa405d11f64a2fb5942dd)) + +## [5.0.1](https://github.com/googleapis/nodejs-logging-bunyan/compare/v5.0.0...v5.0.1) (2023-10-30) + + +### Bug Fixes + +* **deps:** Update dependency @google-cloud/logging to v11 ([d740098](https://github.com/googleapis/nodejs-logging-bunyan/commit/d74009850b316d57a686637f3b0242fa72339000)) + +## [5.0.0](https://github.com/googleapis/nodejs-logging-bunyan/compare/v4.2.2...v5.0.0) (2023-08-10) + + +### ⚠ BREAKING CHANGES + +* upgrade to Node 14 ([#705](https://github.com/googleapis/nodejs-logging-bunyan/issues/705)) + +### Miscellaneous Chores + +* Upgrade to Node 14 ([#705](https://github.com/googleapis/nodejs-logging-bunyan/issues/705)) ([e2299d6](https://github.com/googleapis/nodejs-logging-bunyan/commit/e2299d66ec80a8d5b5c9f15c36b071771e28f9b9)) + +## [4.2.2](https://github.com/googleapis/nodejs-logging-bunyan/compare/v4.2.1...v4.2.2) (2022-12-02) + + +### Bug Fixes + +* Add a partner team as approvers for PRs ([#677](https://github.com/googleapis/nodejs-logging-bunyan/issues/677)) ([7b88e97](https://github.com/googleapis/nodejs-logging-bunyan/commit/7b88e97aab7b70e0ccdf3c388edde013ac64707f)) + +## [4.2.1](https://github.com/googleapis/nodejs-logging-bunyan/compare/v4.2.0...v4.2.1) (2022-11-07) + + +### Bug Fixes + +* Switch instrumentation code to return version stored in NODEJS_BUNYAN_DEFAULT_LIBRARY_VERSION ([#672](https://github.com/googleapis/nodejs-logging-bunyan/issues/672)) ([2eb88d8](https://github.com/googleapis/nodejs-logging-bunyan/commit/2eb88d878583a2a8f823fa310353eeb6be931f7a)) + +## [4.2.0](https://github.com/googleapis/nodejs-logging-bunyan/compare/v4.1.5...v4.2.0) (2022-11-04) + + +### Features + +* Add support for instrumentation version annotations ([#670](https://github.com/googleapis/nodejs-logging-bunyan/issues/670)) ([e332a76](https://github.com/googleapis/nodejs-logging-bunyan/commit/e332a7626dd637daab4e23b36ca7249684506501)) + +## [4.1.5](https://github.com/googleapis/nodejs-logging-bunyan/compare/v4.1.4...v4.1.5) (2022-11-01) + + +### Bug Fixes + +* Prevent instrumentation crash and fix the system test ([#666](https://github.com/googleapis/nodejs-logging-bunyan/issues/666)) ([4e12496](https://github.com/googleapis/nodejs-logging-bunyan/commit/4e12496589123995ebf8e1c54a613c1d2ed565c5)) + +## [4.1.4](https://github.com/googleapis/nodejs-logging-bunyan/compare/v4.1.3...v4.1.4) (2022-10-12) + + +### Bug Fixes + +* Instrumentation performance ([#661](https://github.com/googleapis/nodejs-logging-bunyan/issues/661)) ([c0338fb](https://github.com/googleapis/nodejs-logging-bunyan/commit/c0338fba10e1a60373fdc74739f9237d0266413b)) + +## [4.1.3](https://github.com/googleapis/nodejs-logging-bunyan/compare/v4.1.2...v4.1.3) (2022-10-10) + + +### Bug Fixes + +* Skip parent request entry on cloud run ([#658](https://github.com/googleapis/nodejs-logging-bunyan/issues/658)) ([226972e](https://github.com/googleapis/nodejs-logging-bunyan/commit/226972e12aec94936f4d0b00277cbff05971726e)) + +## [4.1.2](https://github.com/googleapis/nodejs-logging-bunyan/compare/v4.1.1...v4.1.2) (2022-08-25) + + +### Bug Fixes + +* remove pip install statements ([#1546](https://github.com/googleapis/nodejs-logging-bunyan/issues/1546)) ([#649](https://github.com/googleapis/nodejs-logging-bunyan/issues/649)) ([0fdd9ec](https://github.com/googleapis/nodejs-logging-bunyan/commit/0fdd9ec12ad76518e630d0f51f69e34a78b6e693)) +* Update latest logging-nodejs to repaire google-gax vulnerability ([#651](https://github.com/googleapis/nodejs-logging-bunyan/issues/651)) ([857de69](https://github.com/googleapis/nodejs-logging-bunyan/commit/857de693a20e142a16468a6cb352295acd1249a5)) + +## [4.1.1](https://github.com/googleapis/nodejs-logging-bunyan/compare/v4.1.0...v4.1.1) (2022-07-18) + + +### Bug Fixes + +* Logging to stdout in Cloud Run creates a JSON object as "message" ([#644](https://github.com/googleapis/nodejs-logging-bunyan/issues/644)) ([41eaaa8](https://github.com/googleapis/nodejs-logging-bunyan/commit/41eaaa8e2dd282e625412a10907c2dd0f13cf1cb)) + +## [4.1.0](https://github.com/googleapis/nodejs-logging-bunyan/compare/v4.0.0...v4.1.0) (2022-06-02) + + +### Features + +* Add support for library instrumentation ([#631](https://github.com/googleapis/nodejs-logging-bunyan/issues/631)) ([39e0193](https://github.com/googleapis/nodejs-logging-bunyan/commit/39e0193bba4adb5945ff21b53002d2402c9505c5)) + +## [4.0.0](https://github.com/googleapis/nodejs-logging-bunyan/compare/v3.3.1...v4.0.0) (2022-05-20) + + +### ⚠ BREAKING CHANGES + +* update library to use Node 12 (#627) + +### Build System + +* update library to use Node 12 ([#627](https://github.com/googleapis/nodejs-logging-bunyan/issues/627)) ([e9d1143](https://github.com/googleapis/nodejs-logging-bunyan/commit/e9d1143ee335b825a3817fc09573da7cac4584c2)) + +### [3.3.1](https://github.com/googleapis/nodejs-logging-bunyan/compare/v3.3.0...v3.3.1) (2022-04-15) + + +### Bug Fixes + +* Reenable staleness bot ([#613](https://github.com/googleapis/nodejs-logging-bunyan/issues/613)) ([1b3f273](https://github.com/googleapis/nodejs-logging-bunyan/commit/1b3f273ea84475ae2d6fabed435775bfea253ae9)) + +## [3.3.0](https://github.com/googleapis/nodejs-logging-bunyan/compare/v3.2.2...v3.3.0) (2022-03-21) + + +### Features + +* Logging provider for Cloud Functions that outputs structured logs to process.stdout ([#605](https://github.com/googleapis/nodejs-logging-bunyan/issues/605)) ([f3ed3aa](https://github.com/googleapis/nodejs-logging-bunyan/commit/f3ed3aa973d0e2cd1f7311ef48a69cbd72df80f1)) + +### [3.2.2](https://github.com/googleapis/nodejs-logging-bunyan/compare/v3.2.1...v3.2.2) (2022-03-09) + + +### Bug Fixes + +* Use defaultCallback in LoggingBunyan class ([#601](https://github.com/googleapis/nodejs-logging-bunyan/issues/601)) ([f4c01ab](https://github.com/googleapis/nodejs-logging-bunyan/commit/f4c01abe9ee46d89494caa03618500f3a11ee78a)) + +### [3.2.1](https://github.com/googleapis/nodejs-logging-bunyan/compare/v3.2.0...v3.2.1) (2022-03-02) + + +### Bug Fixes + +* Update dependency @google-cloud/logging from 9.0.0 to 9.8.0 ([#597](https://github.com/googleapis/nodejs-logging-bunyan/issues/597)) ([a350362](https://github.com/googleapis/nodejs-logging-bunyan/commit/a350362ba78f275e4c294bfdf2ea5d868191e87e)) + +## [3.2.0](https://www.github.com/googleapis/nodejs-logging-bunyan/compare/v3.1.1...v3.2.0) (2021-12-09) + + +### Features + +* add eslintignore for sameple generated code ([#1302](https://www.github.com/googleapis/nodejs-logging-bunyan/issues/1302)) ([#574](https://www.github.com/googleapis/nodejs-logging-bunyan/issues/574)) ([10c6371](https://www.github.com/googleapis/nodejs-logging-bunyan/commit/10c63713d659f36f45eeb23adba554ed159623a5)) + +### [3.1.1](https://www.github.com/googleapis/nodejs-logging-bunyan/compare/v3.1.0...v3.1.1) (2021-09-08) + + +### Bug Fixes + +* **build:** update branch to main ([#560](https://www.github.com/googleapis/nodejs-logging-bunyan/issues/560)) ([4616ad9](https://www.github.com/googleapis/nodejs-logging-bunyan/commit/4616ad9ccbd592f694b33b701eb945146b2fe203)) + +## [3.1.0](https://www.github.com/googleapis/nodejs-logging-bunyan/compare/v3.0.2...v3.1.0) (2021-06-15) + + +### Features + +* add spanId and traceSampled logic ([#543](https://www.github.com/googleapis/nodejs-logging-bunyan/issues/543)) ([548111b](https://www.github.com/googleapis/nodejs-logging-bunyan/commit/548111b87e4817b410cc2d6a13cc468a78aa5f8a)) + +### [3.0.2](https://www.github.com/googleapis/nodejs-logging-bunyan/compare/v3.0.1...v3.0.2) (2021-02-09) + + +### Bug Fixes + +* **deps:** update dependency google-auth-library to v7 ([#513](https://www.github.com/googleapis/nodejs-logging-bunyan/issues/513)) ([07e5830](https://www.github.com/googleapis/nodejs-logging-bunyan/commit/07e5830207f1bbfbe368625c19ee298775403bcb)) + +### [3.0.1](https://www.github.com/googleapis/nodejs-logging-bunyan/compare/v3.0.0...v3.0.1) (2020-09-12) + + +### Bug Fixes + +* **deps:** update dependency yargs to v16 ([#484](https://www.github.com/googleapis/nodejs-logging-bunyan/issues/484)) ([a307179](https://www.github.com/googleapis/nodejs-logging-bunyan/commit/a30717969b0e0cb9726061ff48f4fa7f9b35961e)) + +## [3.0.0](https://www.github.com/googleapis/nodejs-logging-bunyan/compare/v2.0.3...v3.0.0) (2020-05-20) + + +### ⚠ BREAKING CHANGES + +* drop support for node.js 8.x (#440) + +### Bug Fixes + +* apache license URL ([#468](https://www.github.com/googleapis/nodejs-logging-bunyan/issues/468)) ([#436](https://www.github.com/googleapis/nodejs-logging-bunyan/issues/436)) ([51f5182](https://www.github.com/googleapis/nodejs-logging-bunyan/commit/51f518206f52fdc31773e9625a289f9d25f03abb)) +* update HttpRequest to match @google-cloud/logging ([#412](https://www.github.com/googleapis/nodejs-logging-bunyan/issues/412)) ([0c32a6c](https://www.github.com/googleapis/nodejs-logging-bunyan/commit/0c32a6c5014ac1c4ec54103bd610eae75ef2426a)) +* **deps:** update dependency google-auth-library to v6 ([#427](https://www.github.com/googleapis/nodejs-logging-bunyan/issues/427)) ([03c6c8a](https://www.github.com/googleapis/nodejs-logging-bunyan/commit/03c6c8a9ff9003e906aeea8bec806a87fadef2c3)) + + +### Build System + +* drop support for node.js 8.x ([#440](https://www.github.com/googleapis/nodejs-logging-bunyan/issues/440)) ([b816566](https://www.github.com/googleapis/nodejs-logging-bunyan/commit/b81656645e7a17adfdb1fb78de8e2153128fe2a5)) + +### [2.0.3](https://www.github.com/googleapis/nodejs-logging-bunyan/compare/v2.0.2...v2.0.3) (2020-01-24) + + +### Bug Fixes + +* **docs:** bump release level to GA ([#378](https://www.github.com/googleapis/nodejs-logging-bunyan/issues/378)) ([2d99634](https://www.github.com/googleapis/nodejs-logging-bunyan/commit/2d99634ef4950bea81c64f1355b59ace3138b63e)) + +### [2.0.2](https://www.github.com/googleapis/nodejs-logging-bunyan/compare/v2.0.1...v2.0.2) (2019-12-05) + + +### Bug Fixes + +* **deps:** TypeScript 3.7.0 causes breaking change in typings ([#384](https://www.github.com/googleapis/nodejs-logging-bunyan/issues/384)) ([b7c509c](https://www.github.com/googleapis/nodejs-logging-bunyan/commit/b7c509ce00d8436405abe4e19922594825927a13)) + +### [2.0.1](https://www.github.com/googleapis/nodejs-logging-bunyan/compare/v2.0.0...v2.0.1) (2019-12-02) + + +### Bug Fixes + +* **deps:** update dependency yargs to v15 ([#377](https://www.github.com/googleapis/nodejs-logging-bunyan/issues/377)) ([730bac9](https://www.github.com/googleapis/nodejs-logging-bunyan/commit/730bac9fb1b16575318a0eb167d3bba550318704)) +* **docs:** snippets are now replaced in jsdoc comments ([#371](https://www.github.com/googleapis/nodejs-logging-bunyan/issues/371)) ([1cfbf8d](https://www.github.com/googleapis/nodejs-logging-bunyan/commit/1cfbf8deff023c09075d319904a267f13febbaeb)) + +## [2.0.0](https://www.github.com/googleapis/nodejs-logging-bunyan/compare/v1.2.3...v2.0.0) (2019-10-18) + + +### ⚠ BREAKING CHANGES + +* truncate log messages > 250,000 bytes (#365) + +### Features + +* truncate log messages > 250,000 bytes ([#365](https://www.github.com/googleapis/nodejs-logging-bunyan/issues/365)) ([b712f12](https://www.github.com/googleapis/nodejs-logging-bunyan/commit/b712f123e975b94cd3f096a0cdaf05951320ffac)) + +### [1.2.3](https://www.github.com/googleapis/nodejs-logging-bunyan/compare/v1.2.2...v1.2.3) (2019-09-03) + + +### Bug Fixes + +* **deps:** update dependency yargs to v14 ([#346](https://www.github.com/googleapis/nodejs-logging-bunyan/issues/346)) ([93f7deb](https://www.github.com/googleapis/nodejs-logging-bunyan/commit/93f7deb)) +* **docs:** remove anchor from reference doc link ([#348](https://www.github.com/googleapis/nodejs-logging-bunyan/issues/348)) ([6937d45](https://www.github.com/googleapis/nodejs-logging-bunyan/commit/6937d45)) + +### [1.2.2](https://www.github.com/googleapis/nodejs-logging-bunyan/compare/v1.2.1...v1.2.2) (2019-08-13) + + +### Bug Fixes + +* **deps:** update dependency google-auth-library to v5 ([#342](https://www.github.com/googleapis/nodejs-logging-bunyan/issues/342)) ([f560753](https://www.github.com/googleapis/nodejs-logging-bunyan/commit/f560753)) + +### [1.2.1](https://www.github.com/googleapis/nodejs-logging-bunyan/compare/v1.2.0...v1.2.1) (2019-06-26) + + +### Bug Fixes + +* **docs:** link to reference docs section on googleapis.dev ([#338](https://www.github.com/googleapis/nodejs-logging-bunyan/issues/338)) ([c576ae4](https://www.github.com/googleapis/nodejs-logging-bunyan/commit/c576ae4)) + +## [1.2.0](https://www.github.com/googleapis/nodejs-logging-bunyan/compare/v1.1.1...v1.2.0) (2019-06-24) + + +### Features + +* add support for apiEndpoint override ([#336](https://www.github.com/googleapis/nodejs-logging-bunyan/issues/336)) ([5feb3c9](https://www.github.com/googleapis/nodejs-logging-bunyan/commit/5feb3c9)) + +### [1.1.1](https://www.github.com/googleapis/nodejs-logging-bunyan/compare/v1.1.0...v1.1.1) (2019-06-14) + + +### Bug Fixes + +* **docs:** move to new client docs URL ([#331](https://www.github.com/googleapis/nodejs-logging-bunyan/issues/331)) ([644f80c](https://www.github.com/googleapis/nodejs-logging-bunyan/commit/644f80c)) +* should read logging Bunyan not Winston ([#330](https://www.github.com/googleapis/nodejs-logging-bunyan/issues/330)) ([d08c03f](https://www.github.com/googleapis/nodejs-logging-bunyan/commit/d08c03f)) + +## [1.1.0](https://www.github.com/googleapis/nodejs-logging-bunyan/compare/v1.0.0...v1.1.0) (2019-06-05) + + +### Features + +* add .repo-metadata.json, generate docs ([#328](https://www.github.com/googleapis/nodejs-logging-bunyan/issues/328)) ([342a41e](https://www.github.com/googleapis/nodejs-logging-bunyan/commit/342a41e)) + +## [1.0.0](https://www.github.com/googleapis/nodejs-logging-bunyan/compare/v0.10.1...v1.0.0) (2019-05-29) + + +### ⚠ BREAKING CHANGES + +* upgrade engines field to >=8.10.0 (#298) + +### Bug Fixes + +* proper signature for _write[v] ([#287](https://www.github.com/googleapis/nodejs-logging-bunyan/issues/287)) ([8bb305a](https://www.github.com/googleapis/nodejs-logging-bunyan/commit/8bb305a)) +* **deps:** update dependency google-auth-library to v4 ([#308](https://www.github.com/googleapis/nodejs-logging-bunyan/issues/308)) ([e309b7c](https://www.github.com/googleapis/nodejs-logging-bunyan/commit/e309b7c)) + + +### Build System + +* upgrade engines field to >=8.10.0 ([#298](https://www.github.com/googleapis/nodejs-logging-bunyan/issues/298)) ([143933c](https://www.github.com/googleapis/nodejs-logging-bunyan/commit/143933c)) + + +### Features + +* auto-detect service context ([#290](https://www.github.com/googleapis/nodejs-logging-bunyan/issues/290)) ([595a4db](https://www.github.com/googleapis/nodejs-logging-bunyan/commit/595a4db)) + +## v0.10.1 + +03-14-2019 14:58 PDT + +### Bug Fixes +- fix: add missing dep on google-auth-library ([#278](https://github.com/googleapis/nodejs-logging-bunyan/pull/278)) + +## v0.10.0 + +03-12-2019 16:24 PDT + +### New Features +- feat(middleware): generate parent request logs ([#235](https://github.com/googleapis/nodejs-logging-bunyan/pull/235)) + +### Bug Fixes +- fix: do not promote record.labels if improper ([#266](https://github.com/googleapis/nodejs-logging-bunyan/pull/266)) +- fix: remove deep dependency into auth-library ([#251](https://github.com/googleapis/nodejs-logging-bunyan/pull/251)) + +### Documentation +- docs: update links in contrib guide ([#263](https://github.com/googleapis/nodejs-logging-bunyan/pull/263)) +- docs: update contributing path in README ([#256](https://github.com/googleapis/nodejs-logging-bunyan/pull/256)) +- docs: move CONTRIBUTING.md to root ([#255](https://github.com/googleapis/nodejs-logging-bunyan/pull/255)) +- docs: add lint/fix example to contributing guide ([#252](https://github.com/googleapis/nodejs-logging-bunyan/pull/252)) +- docs: use https for links ([#246](https://github.com/googleapis/nodejs-logging-bunyan/pull/246)) + +### Internal / Testing Changes +- chore: allow custom port in samples tests ([#272](https://github.com/googleapis/nodejs-logging-bunyan/pull/272)) +- build: Add docuploader credentials to node publish jobs ([#270](https://github.com/googleapis/nodejs-logging-bunyan/pull/270)) +- build: update release config ([#268](https://github.com/googleapis/nodejs-logging-bunyan/pull/268)) +- build: use node10 to run samples-test, system-test etc ([#269](https://github.com/googleapis/nodejs-logging-bunyan/pull/269)) +- chore(deps): update dependency mocha to v6 ([#264](https://github.com/googleapis/nodejs-logging-bunyan/pull/264)) +- build: use linkinator for docs test ([#262](https://github.com/googleapis/nodejs-logging-bunyan/pull/262)) +- fix: de-flake system tests ([#261](https://github.com/googleapis/nodejs-logging-bunyan/pull/261)) +- chore: resolve TODO in test ([#259](https://github.com/googleapis/nodejs-logging-bunyan/pull/259)) +- build: create docs test npm scripts ([#258](https://github.com/googleapis/nodejs-logging-bunyan/pull/258)) +- build: test using @grpc/grpc-js in CI ([#257](https://github.com/googleapis/nodejs-logging-bunyan/pull/257)) +- chore(deps): update dependency @google-cloud/common to ^0.31.0 ([#253](https://github.com/googleapis/nodejs-logging-bunyan/pull/253)) +- fix(deps): update dependency yargs to v13 ([#260](https://github.com/googleapis/nodejs-logging-bunyan/pull/260)) +- fix: directly depend on @g-c/common ([#250](https://github.com/googleapis/nodejs-logging-bunyan/pull/250)) +- chore(deps): update dependency eslint-config-prettier to v4 ([#245](https://github.com/googleapis/nodejs-logging-bunyan/pull/245)) +- build: ignore googleapis.com in doc link check ([#242](https://github.com/googleapis/nodejs-logging-bunyan/pull/242)) +- build: check broken links in generated docs ([#239](https://github.com/googleapis/nodejs-logging-bunyan/pull/239)) +- refactor: modernize the sample tests ([#237](https://github.com/googleapis/nodejs-logging-bunyan/pull/237)) + +## v0.9.5 + +12-12-2018 19:50 PST + +### Bug Fixes +- fix(middleware): use bunyan log.child ([#163](https://github.com/googleapis/nodejs-logging-bunyan/pull/163)) +- fix: Don't publish sourcemaps ([#166](https://github.com/googleapis/nodejs-logging-bunyan/pull/166)) + +### Dependencies +- fix(deps): update dependency @opencensus/propagation-stackdriver to ^0.0.6 ([#192](https://github.com/googleapis/nodejs-logging-bunyan/pull/192)) +- fix(deps): update dependency @opencensus/propagation-stackdriver to ^0.0.5 ([#177](https://github.com/googleapis/nodejs-logging-bunyan/pull/177)) + +### Docs & Samples +- docs: update readme badges ([#213](https://github.com/googleapis/nodejs-logging-bunyan/pull/213)) +- refactor: convert samples test from ava to mocha ([#190](https://github.com/googleapis/nodejs-logging-bunyan/pull/190)) + +### Internal / Testing Changes +- chore: the sample tests complete correctly ([#232](https://github.com/googleapis/nodejs-logging-bunyan/pull/232)) +- chore(build): inject yoshi automation key ([#231](https://github.com/googleapis/nodejs-logging-bunyan/pull/231)) +- chore: update nyc and eslint configs ([#230](https://github.com/googleapis/nodejs-logging-bunyan/pull/230)) +- chore: fix publish.sh permission +x ([#228](https://github.com/googleapis/nodejs-logging-bunyan/pull/228)) +- fix(build): fix Kokoro release script ([#227](https://github.com/googleapis/nodejs-logging-bunyan/pull/227)) +- build: add Kokoro configs for autorelease ([#226](https://github.com/googleapis/nodejs-logging-bunyan/pull/226)) +- chore: always nyc report before calling codecov ([#222](https://github.com/googleapis/nodejs-logging-bunyan/pull/222)) +- chore: nyc ignore build/test by default ([#220](https://github.com/googleapis/nodejs-logging-bunyan/pull/220)) +- refactor: reduce the number of dependencies ([#206](https://github.com/googleapis/nodejs-logging-bunyan/pull/206)) +- chore: clean up usage of prettier and eslint ([#219](https://github.com/googleapis/nodejs-logging-bunyan/pull/219)) +- chore: update system tests key ([#216](https://github.com/googleapis/nodejs-logging-bunyan/pull/216)) +- chore: add polling to system tests ([#217](https://github.com/googleapis/nodejs-logging-bunyan/pull/217)) +- chore: update license file ([#215](https://github.com/googleapis/nodejs-logging-bunyan/pull/215)) +- fix(build): fix system key decryption ([#211](https://github.com/googleapis/nodejs-logging-bunyan/pull/211)) +- chore(deps): update dependency typescript to ~3.2.0 ([#209](https://github.com/googleapis/nodejs-logging-bunyan/pull/209)) +- chore: update system tests key ([#210](https://github.com/googleapis/nodejs-logging-bunyan/pull/210)) +- chore: add synth.metadata +- chore(deps): update dependency gts to ^0.9.0 ([#202](https://github.com/googleapis/nodejs-logging-bunyan/pull/202)) +- chore: update eslintignore config ([#201](https://github.com/googleapis/nodejs-logging-bunyan/pull/201)) +- chore(deps): update dependency @google-cloud/nodejs-repo-tools to v3 ([#200](https://github.com/googleapis/nodejs-logging-bunyan/pull/200)) +- chore: drop contributors from multiple places ([#199](https://github.com/googleapis/nodejs-logging-bunyan/pull/199)) +- refactor(middleware): use common code from logging ([#197](https://github.com/googleapis/nodejs-logging-bunyan/pull/197)) +- cleanup: remove unnecessary deps and files ([#198](https://github.com/googleapis/nodejs-logging-bunyan/pull/198)) +- chore: use latest npm on Windows ([#196](https://github.com/googleapis/nodejs-logging-bunyan/pull/196)) +- chore: update CircleCI config ([#195](https://github.com/googleapis/nodejs-logging-bunyan/pull/195)) +- chore: include build in eslintignore ([#191](https://github.com/googleapis/nodejs-logging-bunyan/pull/191)) +- chore(deps): update dependency eslint-plugin-node to v8 ([#186](https://github.com/googleapis/nodejs-logging-bunyan/pull/186)) +- fix(deps): update dependency @google-cloud/common to ^0.26.0 ([#181](https://github.com/googleapis/nodejs-logging-bunyan/pull/181)) +- chore: update issue templates ([#185](https://github.com/googleapis/nodejs-logging-bunyan/pull/185)) +- chore: remove old issue template ([#183](https://github.com/googleapis/nodejs-logging-bunyan/pull/183)) +- build: run tests on node11 ([#182](https://github.com/googleapis/nodejs-logging-bunyan/pull/182)) +- chores(build): do not collect sponge.xml from windows builds ([#180](https://github.com/googleapis/nodejs-logging-bunyan/pull/180)) +- chores(build): run codecov on continuous builds ([#179](https://github.com/googleapis/nodejs-logging-bunyan/pull/179)) +- chore: update new issue template ([#178](https://github.com/googleapis/nodejs-logging-bunyan/pull/178)) +- build: fix codecov uploading on Kokoro ([#174](https://github.com/googleapis/nodejs-logging-bunyan/pull/174)) +- Update kokoro config ([#171](https://github.com/googleapis/nodejs-logging-bunyan/pull/171)) +- chore(deps): update dependency eslint-plugin-prettier to v3 ([#170](https://github.com/googleapis/nodejs-logging-bunyan/pull/170)) +- chore(deps): update dependency typescript to ~3.1.0 ([#169](https://github.com/googleapis/nodejs-logging-bunyan/pull/169)) + +## v0.9.4 + +### Documentation +- fix: doc string is malformed for jsdoc ([#164](https://github.com/googleapis/nodejs-logging-bunyan/pull/164)) + +## v0.9.2 + +### Bug Fixes +- fix: logged errors are reported to error reporting ([#122](https://github.com/googleapis/nodejs-logging-bunyan/pull/122)) +- chore: fix `stream` example in README ([#134](https://github.com/googleapis/nodejs-logging-bunyan/pull/134)) +- fix(deps): update @google-cloud/logging to 4.x ([#152](https://github.com/googleapis/nodejs-logging-bunyan/pull/152)) + +### Dependencies +- fix(deps): update dependency @google-cloud/common to ^0.25.0 ([#150](https://github.com/googleapis/nodejs-logging-bunyan/pull/150)) +- chore(deps): update dependency delay to v4 ([#142](https://github.com/googleapis/nodejs-logging-bunyan/pull/142)) +- fix(deps): update dependency @google-cloud/common to ^0.24.0 ([#146](https://github.com/googleapis/nodejs-logging-bunyan/pull/146)) +- fix(deps): update dependency @opencensus/propagation-stackdriver to ^0.0.4 ([#141](https://github.com/googleapis/nodejs-logging-bunyan/pull/141)) +- fix(deps): update dependency @google-cloud/common to ^0.23.0 ([#139](https://github.com/googleapis/nodejs-logging-bunyan/pull/139)) +- fix(deps): update samples dependency @google-cloud/logging-bunyan to ^0.9.0 ([#137](https://github.com/googleapis/nodejs-logging-bunyan/pull/137)) +- chore(deps): update dependency execa to v1 ([#138](https://github.com/googleapis/nodejs-logging-bunyan/pull/138)) + +### Internal / Testing Changes +- Enable prefer-const in the eslint config ([#151](https://github.com/googleapis/nodejs-logging-bunyan/pull/151)) +- Enable no-var in eslint ([#149](https://github.com/googleapis/nodejs-logging-bunyan/pull/149)) +- Update CI config ([#147](https://github.com/googleapis/nodejs-logging-bunyan/pull/147)) +- Add synth script and update CI ([#144](https://github.com/googleapis/nodejs-logging-bunyan/pull/144)) +- Retry npm install in CI ([#143](https://github.com/googleapis/nodejs-logging-bunyan/pull/143)) +- chore(deps): update dependency nyc to v13 ([#140](https://github.com/googleapis/nodejs-logging-bunyan/pull/140)) + +## v0.9.1 + +### Fixes +- chore: fix install tests (#133) + +## v0.9.0 + +### Breaking changes +- fix: drop support for node.js 4.x and 9.x (#87) +- fix: drop support for node 4.x and 9.x (#69) + +### Features +- feat: request-correlating middleware (#63) + +### Docs & Samples +- chore: require node 8 for samples (#107) +- doc: add express middleware to README (#97) +- doc: fix typo in samples/README (#99) +- fix: fix linting errors in the samples (#100) +- feat(samples): add express middleware sample (#95) +- fix(samples): fix non-working explit setup sample (#93) +- doc: fix usage of logger.info in README.md (#83) +- doc: fix link to HttpRequest message (#68) + +### Dependency updates +- chore(deps): upgrade to @g-c/logging@3.0.1 (#126) +- chore: pin to delay@3.0.x (#127) +- chore(deps): update dependency execa to ^0.11.0 (#125) +- chore(deps): update dependency eslint-config-prettier to v3 (#120) +- chore(deps): update dependency pify to v4 (#119) +- chore(deps): update dependency got to v9 (#114) +- fix(deps): update dependency @opencensus/propagation-stackdriver to ^0.0.3 (#112) +- chore(deps): update dependency typescript to v3 (#109) +- chore(deps): update dependency eslint-plugin-node to v7 (#96) +- chore(deps): update dependency gts to ^0.8.0 (#92) +- fix(deps): update dependency @google-cloud/logging to v2 (#86) +- fix(deps): update dependency yargs to v12 (#82) +- chore: update dependencies (#81) +- Configure Renovate (#74) +- chore(package): missing @types/node dev dependency (#75) +- chore: update all dependencies (#71) +- chore(package): upgrade gts and typescript (#70) + +### Keepin' the lights on +- chore: ignore package-lock.json (#118) +- chore: update renovate config (#113) +- remove that whitespace (#111) +- chore: assert.deelEqual => assert.deepStrictEqual (#108) +- chore: move mocha options to mocha.opts (#106) +- chore: re-enable codecov && drop greenkeeper badge (#98) +- refactor: drop repo-tool as an exec wrapper (#79) +- chore: update sample lockfiles (#78) +- fix: update linking for samples (#77) +- cleanup: remove some casts (#76) +- fix: the system tests use a custom log (#73) +- test: use source-map-support (#72) +- chore: remove `--bail` from the system tests config (#67) +- chore: the ultimate fix for repo-tools EPERM (#64) diff --git a/handwritten/nodejs-logging-bunyan/CODE_OF_CONDUCT.md b/handwritten/nodejs-logging-bunyan/CODE_OF_CONDUCT.md new file mode 100644 index 00000000000..2add2547a81 --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/CODE_OF_CONDUCT.md @@ -0,0 +1,94 @@ + +# Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of +experience, education, socio-economic status, nationality, personal appearance, +race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, or to ban temporarily or permanently any +contributor for other behaviors that they deem inappropriate, threatening, +offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +This Code of Conduct also applies outside the project spaces when the Project +Steward has a reasonable belief that an individual's behavior may have a +negative impact on the project or its community. + +## Conflict Resolution + +We do not believe that all conflict is bad; healthy debate and disagreement +often yield positive results. However, it is never okay to be disrespectful or +to engage in behavior that violates the project’s code of conduct. + +If you see someone violating the code of conduct, you are encouraged to address +the behavior directly with those involved. Many issues can be resolved quickly +and easily, and this gives people more control over the outcome of their +dispute. If you are unable to resolve the matter for any reason, or if the +behavior is threatening or harassing, report it. We are dedicated to providing +an environment where participants feel welcome and safe. + +Reports should be directed to *googleapis-stewards@google.com*, the +Project Steward(s) for *Google Cloud Client Libraries*. It is the Project Steward’s duty to +receive and address reported violations of the code of conduct. They will then +work with a committee consisting of representatives from the Open Source +Programs Office and the Google Open Source Strategy team. If for any reason you +are uncomfortable reaching out to the Project Steward, please email +opensource@google.com. + +We will investigate every complaint, but you may not receive a direct response. +We will use our discretion in determining when and how to follow up on reported +incidents, which may range from not taking action to permanent expulsion from +the project and project-sponsored spaces. We will notify the accused of the +report and provide them an opportunity to discuss it before any action is taken. +The identity of the reporter will be omitted from the details of the report +supplied to the accused. In potentially harmful situations, such as ongoing +harassment or threats to anyone's safety, we may take action without notice. + +## Attribution + +This Code of Conduct is adapted from the Contributor Covenant, version 1.4, +available at +https://www.contributor-covenant.org/version/1/4/code-of-conduct.html \ No newline at end of file diff --git a/handwritten/nodejs-logging-bunyan/CONTRIBUTING.md b/handwritten/nodejs-logging-bunyan/CONTRIBUTING.md new file mode 100644 index 00000000000..d9f72ea43ba --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/CONTRIBUTING.md @@ -0,0 +1,75 @@ +# How to become a contributor and submit your own code + +**Table of contents** + +* [Contributor License Agreements](#contributor-license-agreements) +* [Contributing a patch](#contributing-a-patch) +* [Running the tests](#running-the-tests) +* [Releasing the library](#releasing-the-library) + +## Contributor License Agreements + +We'd love to accept your sample apps and patches! Before we can take them, we +have to jump a couple of legal hurdles. + +Please fill out either the individual or corporate Contributor License Agreement +(CLA). + + * If you are an individual writing original source code and you're sure you + own the intellectual property, then you'll need to sign an [individual CLA](https://developers.google.com/open-source/cla/individual). + * If you work for a company that wants to allow you to contribute your work, + then you'll need to sign a [corporate CLA](https://developers.google.com/open-source/cla/corporate). + +Follow either of the two links above to access the appropriate CLA and +instructions for how to sign and return it. Once we receive it, we'll be able to +accept your pull requests. + +## Contributing A Patch + +1. Submit an issue describing your proposed change to the repo in question. +1. The repo owner will respond to your issue promptly. +1. If your proposed change is accepted, and you haven't already done so, sign a + Contributor License Agreement (see details above). +1. Fork the desired repo, develop and test your code changes. +1. Ensure that your code adheres to the existing style in the code to which + you are contributing. +1. Ensure that your code has an appropriate set of tests which all pass. +1. Title your pull request following [Conventional Commits](https://www.conventionalcommits.org/) styling. +1. Submit a pull request. + +### Before you begin + +1. [Select or create a Cloud Platform project][projects]. +1. [Enable the Cloud Logging for Bunyan API][enable_api]. +1. [Set up authentication with a service account][auth] so you can access the + API from your local workstation. + + +## Running the tests + +1. [Prepare your environment for Node.js setup][setup]. + +1. Install dependencies: + + npm install + +1. Run the tests: + + # Run unit tests. + npm test + + # Run sample integration tests. + npm run samples-test + + # Run all system tests. + npm run system-test + +1. Lint (and maybe fix) any changes: + + npm run fix + +[setup]: https://cloud.google.com/nodejs/docs/setup +[projects]: https://console.cloud.google.com/project +[billing]: https://support.google.com/cloud/answer/6293499#enable-billing +[enable_api]: https://console.cloud.google.com/flows/enableapi?apiid=logging.googleapis.com +[auth]: https://cloud.google.com/docs/authentication/getting-started \ No newline at end of file diff --git a/handwritten/nodejs-logging-bunyan/LICENSE b/handwritten/nodejs-logging-bunyan/LICENSE new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/handwritten/nodejs-logging-bunyan/README.md b/handwritten/nodejs-logging-bunyan/README.md new file mode 100644 index 00000000000..47e3b1c270a --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/README.md @@ -0,0 +1,349 @@ +[//]: # "This README.md file is auto-generated, all changes to this file will be lost." +[//]: # "To regenerate it, use `python -m synthtool`." +Google Cloud Platform logo + +# [Cloud Logging for Bunyan: Node.js Client](https://github.com/googleapis/nodejs-logging-bunyan) + +[![release level](https://img.shields.io/badge/release%20level-stable-brightgreen.svg?style=flat)](https://cloud.google.com/terms/launch-stages) +[![npm version](https://img.shields.io/npm/v/@google-cloud/logging-bunyan.svg)](https://www.npmjs.org/package/@google-cloud/logging-bunyan) + + + + +This module provides an easy to use, higher-level layer for working with [Cloud Logging](https://cloud.google.com/logging/docs), +compatible with [Bunyan](https://www.npmjs.com/package/bunyan). Simply attach this as a transport to your existing Bunyan loggers. + + +A comprehensive list of changes in each version may be found in +[the CHANGELOG](https://github.com/googleapis/nodejs-logging-bunyan/blob/main/CHANGELOG.md). + +* [Cloud Logging for Bunyan Node.js Client API Reference][client-docs] +* [Cloud Logging for Bunyan Documentation][product-docs] +* [github.com/googleapis/nodejs-logging-bunyan](https://github.com/googleapis/nodejs-logging-bunyan) + +Read more about the client libraries for Cloud APIs, including the older +Google APIs Client Libraries, in [Client Libraries Explained][explained]. + +[explained]: https://cloud.google.com/apis/docs/client-libraries-explained + +**Table of contents:** + + +* [Quickstart](#quickstart) + * [Before you begin](#before-you-begin) + * [Installing the client library](#installing-the-client-library) + * [Using the client library](#using-the-client-library) +* [Samples](#samples) +* [Versioning](#versioning) +* [Contributing](#contributing) +* [License](#license) + +## Quickstart + +### Before you begin + +1. [Select or create a Cloud Platform project][projects]. +1. [Enable the Cloud Logging for Bunyan API][enable_api]. +1. [Set up authentication][auth] so you can access the + API from your local workstation. + +### Installing the client library + +```bash +npm install @google-cloud/logging-bunyan +``` + + +### Using the client library + +```javascript +const bunyan = require('bunyan'); + +// Imports the Google Cloud client library for Bunyan +const {LoggingBunyan} = require('@google-cloud/logging-bunyan'); + +// Creates a Bunyan Cloud Logging client +const loggingBunyan = new LoggingBunyan(); + +// Create a Bunyan logger that streams to Cloud Logging +// Logs will be written to: "projects/YOUR_PROJECT_ID/logs/bunyan_log" +const logger = bunyan.createLogger({ + // The JSON payload of the log as it appears in Cloud Logging + // will contain "name": "my-service" + name: 'my-service', + streams: [ + // Log to the console at 'info' and above + {stream: process.stdout, level: 'info'}, + // And log to Cloud Logging, logging at 'info' and above + loggingBunyan.stream('info'), + ], +}); + +// Writes some log entries +logger.error('warp nacelles offline'); +logger.info('shields at 99%'); + +``` +### Using as an express middleware + +***NOTE: this feature is experimental. The API may change in a backwards +incompatible way until this is deemed stable. Please provide us feedback so +that we can better refine this express integration.*** + +We provide a middleware that can be used in an express application. Apart from +being easy to use, this enables some more powerful features of Cloud +Logging: request bundling. Any application logs emitted on behalf of a specific +request will be shown nested inside the request log as you see in this +screenshot: + +![Request Bundling Example](https://raw.githubusercontent.com/googleapis/nodejs-logging-bunyan/master/doc/images/request-bundling.png) + +The middleware adds a `bunyan`-style log function to the `request` object. You +can use this wherever you have access to the `request` object (`req` in the +sample below). All log entries that are made on behalf of a specific request are +shown bundled together in the Cloud Logging UI. + +```javascript +const lb = require('@google-cloud/logging-bunyan'); + +// Import express module and create an http server. +const express = require('express'); + +async function startServer() { + const {logger, mw} = await lb.express.middleware(); + const app = express(); + + // Install the logging middleware. This ensures that a Bunyan-style `log` + // function is available on the `request` object. Attach this as one of the + // earliest middleware to make sure that log function is available in all the + // subsequent middleware and routes. + app.use(mw); + + // Setup an http route and a route handler. + app.get('/', (req, res) => { + // `req.log` can be used as a bunyan style log method. All logs generated + // using `req.log` use the current request context. That is, all logs + // corresponding to a specific request will be bundled in the Cloud UI. + req.log.info('this is an info log message'); + res.send('hello world'); + }); + + // `logger` can be used as a global logger, one not correlated to any specific + // request. + logger.info({port: 8080}, 'bonjour'); + + // Start listening on the http server. + app.listen(8080, () => { + console.log('http server listening on port 8080'); + }); +} + +startServer(); +``` + +### Error Reporting + +Any `Error` objects you log at severity `error` or higher can automatically be picked up by [Cloud Error Reporting](https://cloud.google.com/error-reporting/) if you have specified a `serviceContext.service` when instantiating a `LoggingBunyan` instance: + +```javascript +const loggingBunyan = new LoggingBunyan({ + serviceContext: { + service: 'my-service', // required to report logged errors + // to the Google Cloud Error Reporting + // console + version: 'my-version' + } +}); +``` + +It is an error to specify a `serviceContext` but not specify `serviceContext.service`. + +Make sure to add logs to your [uncaught exception](https://nodejs.org/api/process.html#process_event_uncaughtexception) and [unhandled rejection](https://nodejs.org/api/process.html#process_event_unhandledrejection) handlers if you want to see those errors too. + +You may also want to see the [@google-cloud/error-reporting][@google-cloud/error-reporting] module which provides direct access to the Error Reporting API. + +### Special Payload Fields in LogEntry + +There are some fields that are considered special by Google cloud logging and will be extracted into the LogEntry structure. For example, `severity`, `message` and `labels` can be extracted to LogEntry if included in the bunyan log payload. These [special JSON fields](https://cloud.google.com/logging/docs/structured-logging#special-payload-fields) will be used to set the corresponding fields in the `LogEntry`. Please be aware of these special fields to avoid unexpected logging behavior. + +### LogEntry Labels + +If the bunyan log record contains a label property where all the values are strings, we automatically promote that +property to be the [`LogEntry.labels`](https://cloud.google.com/logging/docs/reference/v2/rpc/google.logging.v2#logentry) value rather +than being one of the properties in the `payload` fields. This makes it easier to filter the logs in the UI using the labels. + +```javascript +logger.info({labels: {someKey: 'some value'}}, 'test log message'); +``` + +All the label values must be strings for this automatic promotion to work. Otherwise the labels are left in the payload. + +### Formatting Request Logs + +To format your request logs you can provide a `httpRequest` property on the bunyan metadata you provide along with the log message. We will treat this as the [`HttpRequest`](https://cloud.google.com/logging/docs/reference/v2/rpc/google.logging.type#google.logging.type.HttpRequest) message and Cloud logging will show this as a request log. Example: + +![Request Log Example](https://raw.githubusercontent.com/googleapis/nodejs-logging-bunyan/master/doc/images/request-log.png) + +```js +logger.info({ + httpRequest: { + status: res.statusCode, + requestUrl: req.url, + requestMethod: req.method, + remoteIp: req.connection.remoteAddress, + // etc. + } +}, req.path); +``` + +The `httpRequest` property must be a properly formatted [`HttpRequest`](https://cloud.google.com/logging/docs/reference/v2/rpc/google.logging.type#google.logging.type.HttpRequest) message. (Note: the linked protobuf documentation shows `snake_case` property names, but in JavaScript one needs to provide property names in `camelCase`.) + +### Correlating Logs with Traces + +If you use [@google-cloud/trace-agent][trace-agent] module, then this module will set the Cloud Logging [LogEntry][LogEntry] `trace` property based on the current trace context when available. That correlation allows you to [view log entries][trace-viewing-log-entries] inline with trace spans in the Cloud Trace Viewer. Example: + +![Logs in Trace Example](https://raw.githubusercontent.com/googleapis/nodejs-logging-bunyan/master/doc/images/bunyan-logs-in-trace.png) + +If you wish to set the Cloud LogEntry `trace` property with a custom value, then write a Bunyan log entry property for `'logging.googleapis.com/trace'`, which is exported by this module as `LOGGING_TRACE_KEY`. For example: + +```js +const bunyan = require('bunyan'); +// Node 6+ +const {LoggingBunyan, LOGGING_TRACE_KEY} = require('@google-cloud/logging-bunyan'); +const loggingBunyan = LoggingBunyan(); + +... + +logger.info({ + [LOGGING_TRACE_KEY]: 'custom-trace-value' +}, 'Bunyan log entry with custom trace field'); +``` + +### Error handling with a default callback +The `LoggingBunyan` class creates an instance of `Logging` which creates the `Log` class from `@google-cloud/logging` package to write log entries. +The `Log` class writes logs asynchronously and there are cases when log entries cannot be written when it fails or an error is returned from Logging backend. +If the error is not handled, it could crash the application. One possible way to handle the error is to provide a default callback +to the `LoggingBunyan` constructor which will be used to initialize the `Log` object with that callback like in the example below: + +```js +// Imports the Google Cloud client library for Bunyan +const {LoggingBunyan} = require('@google-cloud/logging-bunyan'); +// Creates a client +const loggingBunyan = new LoggingBunyan({ + projectId: 'your-project-id', + keyFilename: '/path/to/key.json', + defaultCallback: err => { + if (err) { + console.log('Error occured: ' + err); + } + }, +}); +``` + +### Alternative way to ingest logs in Google Cloud managed environments +If you use this library with the Cloud Logging Agent, you can configure the handler to output logs to `process.stdout` using +the [structured logging Json format](https://cloud.google.com/logging/docs/structured-logging#special-payload-fields). +To do this, add `redirectToStdout: true` parameter to the `LoggingBunyan` constructor as in sample below. +You can use this parameter when running applications in Google Cloud managed environments such as AppEngine, Cloud Run, +Cloud Function or GKE. The logger agent installed on these environments can capture `process.stdout` and ingest it into Cloud Logging. +The agent can parse structured logs printed to `process.stdout` and capture additional log metadata beside the log payload. +It is recommended to set `redirectToStdout: true` in serverless environments like Cloud Functions since it could +decrease logging record loss upon execution termination - since all logs are written to `process.stdout` those +would be picked up by the Cloud Logging Agent running in Google Cloud managed environment. +Note that there is also a `useMessageField` option which controls if "message" field is used to store +structured, non-text data inside `jsonPayload` field when `redirectToStdout` is set. By default `useMessageField` is always `true`. +Set the `skipParentEntryForCloudRun` option to skip creating an entry for the request itself as Cloud Run already automatically creates +such log entries. This might become the default behaviour in a next major version. + +```js +// Imports the Google Cloud client library for Bunyan +const {LoggingBunyan} = require('@google-cloud/logging-bunyan'); + +// Creates a client +const loggingBunyan = new LoggingBunyan({ + projectId: 'your-project-id', + keyFilename: '/path/to/key.json', + redirectToStdout: true, +}); +``` + + +## Samples + +Samples are in the [`samples/`](https://github.com/googleapis/nodejs-logging-bunyan/tree/main/samples) directory. Each sample's `README.md` has instructions for running its sample. + +| Sample | Source Code | Try it | +| --------------------------- | --------------------------------- | ------ | +| Express | [source code](https://github.com/googleapis/nodejs-logging-bunyan/blob/main/samples/express.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-logging-bunyan&page=editor&open_in_editor=samples/express.js,samples/README.md) | +| Quickstart | [source code](https://github.com/googleapis/nodejs-logging-bunyan/blob/main/samples/quickstart.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-logging-bunyan&page=editor&open_in_editor=samples/quickstart.js,samples/README.md) | +| Explict Auth Setup | [source code](https://github.com/googleapis/nodejs-logging-bunyan/blob/main/samples/setup_explicit.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-logging-bunyan&page=editor&open_in_editor=samples/setup_explicit.js,samples/README.md) | + + + +The [Cloud Logging for Bunyan Node.js Client API Reference][client-docs] documentation +also contains samples. + +## Supported Node.js Versions + +Our client libraries follow the [Node.js release schedule](https://github.com/nodejs/release#release-schedule). +Libraries are compatible with all current _active_ and _maintenance_ versions of +Node.js. +If you are using an end-of-life version of Node.js, we recommend that you update +as soon as possible to an actively supported LTS version. + +Google's client libraries support legacy versions of Node.js runtimes on a +best-efforts basis with the following warnings: + +* Legacy versions are not tested in continuous integration. +* Some security patches and features cannot be backported. +* Dependencies cannot be kept up-to-date. + +Client libraries targeting some end-of-life versions of Node.js are available, and +can be installed through npm [dist-tags](https://docs.npmjs.com/cli/dist-tag). +The dist-tags follow the naming convention `legacy-(version)`. +For example, `npm install @google-cloud/logging-bunyan@legacy-8` installs client libraries +for versions compatible with Node.js 8. + +## Versioning + +This library follows [Semantic Versioning](http://semver.org/). + + + +This library is considered to be **stable**. The code surface will not change in backwards-incompatible ways +unless absolutely necessary (e.g. because of critical security issues) or with +an extensive deprecation period. Issues and requests against **stable** libraries +are addressed with the highest priority. + + + + + + +More Information: [Google Cloud Platform Launch Stages][launch_stages] + +[launch_stages]: https://cloud.google.com/terms/launch-stages + +## Contributing + +Contributions welcome! See the [Contributing Guide](https://github.com/googleapis/nodejs-logging-bunyan/blob/main/CONTRIBUTING.md). + +Please note that this `README.md`, the `samples/README.md`, +and a variety of configuration files in this repository (including `.nycrc` and `tsconfig.json`) +are generated from a central template. To edit one of these files, make an edit +to its templates in +[directory](https://github.com/googleapis/synthtool). + +## License + +Apache Version 2.0 + +See [LICENSE](https://github.com/googleapis/nodejs-logging-bunyan/blob/main/LICENSE) + +[client-docs]: https://cloud.google.com/nodejs/docs/reference/logging-bunyan/latest +[product-docs]: https://cloud.google.com/logging +[shell_img]: https://gstatic.com/cloudssh/images/open-btn.png +[projects]: https://console.cloud.google.com/project +[billing]: https://support.google.com/cloud/answer/6293499#enable-billing +[enable_api]: https://console.cloud.google.com/flows/enableapi?apiid=logging.googleapis.com +[auth]: https://cloud.google.com/docs/authentication/external/set-up-adc-local diff --git a/handwritten/nodejs-logging-bunyan/doc/images/bunyan-logs-in-trace.png b/handwritten/nodejs-logging-bunyan/doc/images/bunyan-logs-in-trace.png new file mode 100644 index 00000000000..66a21325bba Binary files /dev/null and b/handwritten/nodejs-logging-bunyan/doc/images/bunyan-logs-in-trace.png differ diff --git a/handwritten/nodejs-logging-bunyan/doc/images/request-bundling.png b/handwritten/nodejs-logging-bunyan/doc/images/request-bundling.png new file mode 100644 index 00000000000..e81e2be34bc Binary files /dev/null and b/handwritten/nodejs-logging-bunyan/doc/images/request-bundling.png differ diff --git a/handwritten/nodejs-logging-bunyan/doc/images/request-log.png b/handwritten/nodejs-logging-bunyan/doc/images/request-log.png new file mode 100644 index 00000000000..a58853afa15 Binary files /dev/null and b/handwritten/nodejs-logging-bunyan/doc/images/request-log.png differ diff --git a/handwritten/nodejs-logging-bunyan/linkinator.config.json b/handwritten/nodejs-logging-bunyan/linkinator.config.json new file mode 100644 index 00000000000..29a223b6db6 --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/linkinator.config.json @@ -0,0 +1,10 @@ +{ + "recurse": true, + "skip": [ + "https://codecov.io/gh/googleapis/", + "www.googleapis.com", + "img.shields.io" + ], + "silent": true, + "concurrency": 10 +} diff --git a/handwritten/nodejs-logging-bunyan/owlbot.py b/handwritten/nodejs-logging-bunyan/owlbot.py new file mode 100644 index 00000000000..3d2b1f5cb8c --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/owlbot.py @@ -0,0 +1,52 @@ +# Copyright 2018 Google LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import synthtool as s +import synthtool.gcp as gcp +import synthtool.languages.node_mono_repo as node +import logging + +logging.basicConfig(level=logging.DEBUG) + + +common_templates = gcp.CommonTemplates() +templates = common_templates.node_library(source_location='build/src') +s.move(templates, excludes=[ + ".github/auto-label.yaml", + ".github/release-please.yml", + ".github/CODEOWNERS", + ".github/sync-repo-settings.yaml", +]) +node.fix() + +# -------------------------------------------------------------------------- +# Modify test configs +# -------------------------------------------------------------------------- + +# add shared environment variables to test configs +s.move( + ".kokoro/common_env_vars.cfg", + ".kokoro/common.cfg", + merge=lambda src, dst, _, : f"{dst}\n{src}", +) +for path, subdirs, files in os.walk(f".kokoro/continuous"): + for name in files: + if name == "common.cfg": + file_path = os.path.join(path, name) + s.move( + ".kokoro/common_env_vars.cfg", + file_path, + merge=lambda src, dst, _, : f"{dst}\n{src}", + ) diff --git a/handwritten/nodejs-logging-bunyan/package.json b/handwritten/nodejs-logging-bunyan/package.json new file mode 100644 index 00000000000..790243df625 --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/package.json @@ -0,0 +1,90 @@ +{ + "name": "@google-cloud/logging-bunyan", + "description": "Cloud Logging stream for Bunyan", + "version": "5.1.0", + "license": "Apache-2.0", + "author": "Google Inc.", + "engines": { + "node": ">=14.0.0" + }, + "repository": { + "type": "git", + "directory": "handwritten/nodejs-logging-bunyan", + "url": "https://github.com/googleapis/google-cloud-node.git" + }, + "main": "./build/src/index.js", + "types": "./build/src/index.d.ts", + "files": [ + "build/src", + "!build/src/**/*.map" + ], + "keywords": [ + "google apis client", + "google api client", + "google apis", + "google api", + "google", + "google cloud platform", + "google cloud", + "cloud", + "google logging", + "logging", + "stackdriver logging", + "stackdriver", + "bunyan stream", + "winston" + ], + "scripts": { + "predocs": "npm run compile", + "docs": "jsdoc -c .jsdoc.js", + "lint": "gts check", + "presamples-test": "npm run compile", + "presystem-test": "npm run compile", + "samples-test": "cd samples/ && npm link ../ && npm test && cd ../", + "system-test": "mocha build/system-test --timeout 600000", + "test": "c8 mocha --recursive build/test", + "clean": "gts clean", + "compile": "tsc -p .", + "postcompile": "cpy \"src/types/*\" build/src/types", + "fix": "gts fix", + "prepare": "npm run compile", + "pretest": "npm run compile", + "docs-test": "linkinator docs", + "predocs-test": "npm run docs", + "prelint": "cd samples; npm link ../; npm install", + "precompile": "gts clean" + }, + "dependencies": { + "@google-cloud/logging": "^11.0.0", + "google-auth-library": "^9.0.0" + }, + "devDependencies": { + "@google-cloud/common": "^5.0.0", + "@types/bunyan": "^1.8.4", + "@types/express": "^4.16.0", + "@types/mocha": "^9.0.0", + "@types/node": "^20.4.9", + "@types/proxyquire": "^1.3.28", + "@types/uuid": "^10.0.0", + "bunyan": "^1.8.12", + "c8": "^9.0.0", + "codecov": "^3.0.2", + "cpy-cli": "^4.0.0", + "delay": "^5.0.0", + "express": "^4.16.3", + "gts": "^5.0.0", + "jsdoc": "^4.0.0", + "jsdoc-fresh": "^3.0.0", + "jsdoc-region-tag": "^3.0.0", + "linkinator": "^3.0.0", + "mocha": "^9.2.2", + "post-install-check": "0.0.1", + "proxyquire": "^2.0.1", + "typescript": "^5.1.6", + "uuid": "^10.0.0" + }, + "peerDependencies": { + "bunyan": "*" + }, + "homepage": "https://github.com/googleapis/google-cloud-node/tree/main/handwritten/nodejs-logging-bunyan" +} diff --git a/handwritten/nodejs-logging-bunyan/samples/.eslintrc.yml b/handwritten/nodejs-logging-bunyan/samples/.eslintrc.yml new file mode 100644 index 00000000000..282535f55f6 --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/samples/.eslintrc.yml @@ -0,0 +1,3 @@ +--- +rules: + no-console: off diff --git a/handwritten/nodejs-logging-bunyan/samples/README.md b/handwritten/nodejs-logging-bunyan/samples/README.md new file mode 100644 index 00000000000..c05b0a96bb5 --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/samples/README.md @@ -0,0 +1,87 @@ +[//]: # "This README.md file is auto-generated, all changes to this file will be lost." +[//]: # "To regenerate it, use `python -m synthtool`." +Google Cloud Platform logo + +# [Cloud Logging for Bunyan: Node.js Samples](https://github.com/googleapis/nodejs-logging-bunyan) + +[![Open in Cloud Shell][shell_img]][shell_link] + +This module provides an easy to use, higher-level layer for working with [Cloud Logging](https://cloud.google.com/logging/docs), +compatible with [Bunyan](https://www.npmjs.com/package/bunyan). Simply attach this as a transport to your existing Bunyan loggers. + +## Table of Contents + +* [Before you begin](#before-you-begin) +* [Samples](#samples) + * [Express](#express) + * [Quickstart](#quickstart) + * [Explict Auth Setup](#explict-auth-setup) + +## Before you begin + +Before running the samples, make sure you've followed the steps outlined in +[Using the client library](https://github.com/googleapis/nodejs-logging-bunyan#using-the-client-library). + +`cd samples` + +`npm install` + +`cd ..` + +## Samples + + + +### Express + +View the [source code](https://github.com/googleapis/nodejs-logging-bunyan/blob/main/samples/express.js). + +[![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-logging-bunyan&page=editor&open_in_editor=samples/express.js,samples/README.md) + +__Usage:__ + + +`node samples/express.js` + + +----- + + + + +### Quickstart + +View the [source code](https://github.com/googleapis/nodejs-logging-bunyan/blob/main/samples/quickstart.js). + +[![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-logging-bunyan&page=editor&open_in_editor=samples/quickstart.js,samples/README.md) + +__Usage:__ + + +`node samples/quickstart.js` + + +----- + + + + +### Explict Auth Setup + +View the [source code](https://github.com/googleapis/nodejs-logging-bunyan/blob/main/samples/setup_explicit.js). + +[![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-logging-bunyan&page=editor&open_in_editor=samples/setup_explicit.js,samples/README.md) + +__Usage:__ + + +`node samples/setup_explicit.js` + + + + + + +[shell_img]: https://gstatic.com/cloudssh/images/open-btn.png +[shell_link]: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-logging-bunyan&page=editor&open_in_editor=samples/README.md +[product-docs]: https://cloud.google.com/logging diff --git a/handwritten/nodejs-logging-bunyan/samples/express.js b/handwritten/nodejs-logging-bunyan/samples/express.js new file mode 100644 index 00000000000..2c7d4e28a4f --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/samples/express.js @@ -0,0 +1,63 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// [START logging_bunyan_express] +// Imports the Google Cloud client library for Bunyan. +const lb = require('@google-cloud/logging-bunyan'); + +// Import express module and create an http server. +const express = require('express'); + +async function startServer() { + const {logger, mw} = await lb.express.middleware({ + logName: 'samples_express', + }); + const app = express(); + + // Install the logging middleware. This ensures that a Bunyan-style `log` + // function is available on the `request` object. This should be the very + // first middleware you attach to your app. + app.use(mw); + + // Setup an http route and a route handler. + app.get('/', (req, res) => { + // `req.log` can be used as a bunyan style log method. All logs generated + // using `req.log` use the current request context. That is, all logs + // corresponding to a specific request will be bundled in the Stackdriver + // UI. + req.log.info('this is an info log message'); + res.send('hello world'); + }); + + const port = process.env.PORT || 8080; + + // `logger` can be used as a global logger, one not correlated to any specific + // request. + logger.info({port}, 'bonjour'); + + // Start listening on the http server. + const server = app.listen(port, () => { + console.log(`http server listening on port ${port}`); + }); + + app.get('/shutdown', (req, res) => { + res.sendStatus(200); + server.close(); + }); +} + +startServer(); +// [END logging_bunyan_express] diff --git a/handwritten/nodejs-logging-bunyan/samples/package.json b/handwritten/nodejs-logging-bunyan/samples/package.json new file mode 100644 index 00000000000..8281c8de5e4 --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/samples/package.json @@ -0,0 +1,30 @@ +{ + "name": "nodejs-docs-samples-logging-bunyan", + "files": [ + "*.js", + "!test/" + ], + "private": true, + "license": "Apache-2.0", + "author": "Google Inc.", + "repository": "googleapis/nodejs-logging-bunyan", + "engines": { + "node": ">=14.0.0" + }, + "scripts": { + "test": "mocha system-test --timeout 600000" + }, + "dependencies": { + "@google-cloud/logging-bunyan": "^5.1.0", + "bunyan": "^1.8.12", + "express": "^4.16.3", + "yargs": "^16.0.0" + }, + "devDependencies": { + "@google-cloud/logging": "^11.0.0", + "chai": "^4.2.0", + "delay": "^5.0.0", + "got": "^11.0.0", + "mocha": "^8.0.0" + } +} \ No newline at end of file diff --git a/handwritten/nodejs-logging-bunyan/samples/quickstart.js b/handwritten/nodejs-logging-bunyan/samples/quickstart.js new file mode 100644 index 00000000000..6f58c7fde2a --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/samples/quickstart.js @@ -0,0 +1,43 @@ +// Copyright 2017 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// [START logging_bunyan_quickstart] +const bunyan = require('bunyan'); + +// Imports the Google Cloud client library for Bunyan +const {LoggingBunyan} = require('@google-cloud/logging-bunyan'); + +// Creates a Bunyan Cloud Logging client +const loggingBunyan = new LoggingBunyan(); + +// Create a Bunyan logger that streams to Cloud Logging +// Logs will be written to: "projects/YOUR_PROJECT_ID/logs/bunyan_log" +const logger = bunyan.createLogger({ + // The JSON payload of the log as it appears in Cloud Logging + // will contain "name": "my-service" + name: 'my-service', + streams: [ + // Log to the console at 'info' and above + {stream: process.stdout, level: 'info'}, + // And log to Cloud Logging, logging at 'info' and above + loggingBunyan.stream('info'), + ], +}); + +// Writes some log entries +logger.error('warp nacelles offline'); +logger.info('shields at 99%'); +// [END logging_bunyan_quickstart] diff --git a/handwritten/nodejs-logging-bunyan/samples/setup_explicit.js b/handwritten/nodejs-logging-bunyan/samples/setup_explicit.js new file mode 100644 index 00000000000..4010751d092 --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/samples/setup_explicit.js @@ -0,0 +1,31 @@ +// Copyright 2017 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// sample-metadata: +// title: Explict Auth Setup + +/* eslint-disable no-unused-vars */ +// [START logging_bunyan_setup_explicit] +// Imports the Google Cloud client library for Bunyan +const {LoggingBunyan} = require('@google-cloud/logging-bunyan'); + +// Creates a client +const loggingBunyan = new LoggingBunyan({ + projectId: 'your-project-id', + keyFilename: '/path/to/key.json', +}); +// [END logging_bunyan_setup_explicit] +/* eslint-enable no-unused-vars */ diff --git a/handwritten/nodejs-logging-bunyan/samples/system-test/express.test.js b/handwritten/nodejs-logging-bunyan/samples/system-test/express.test.js new file mode 100644 index 00000000000..d33a968a7f1 --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/samples/system-test/express.test.js @@ -0,0 +1,62 @@ +// Copyright 2017 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +const path = require('path'); +const {assert} = require('chai'); +const {describe, it, before, after} = require('mocha'); +const {spawn} = require('child_process'); +const delay = require('delay'); +const got = require('got'); +const {Logging} = require('@google-cloud/logging'); +const logging = new Logging(); + +const PORT = process.env.PORT || 8080; +const lb = require('@google-cloud/logging-bunyan'); +const {APP_LOG_SUFFIX} = lb.express; + +describe('express samples', () => { + after(() => got(`http://localhost:${PORT}/shutdown`)); + before(async () => { + // Start the express server. + spawn(process.execPath, ['express.js'], { + cwd: path.join(__dirname, '..'), + cleanup: true, // kill child process when parent exits. + }).stdout.pipe(process.stdout); + + // Wait 10 seconds for initialization and for server to start listening. + await delay(10 * 1000); + }); + + it('should write using bunyan', async function () { + this.retries(3); + + // Make an HTTP request to exercise a request logging path. + await got(`http://localhost:${PORT}/`); + + // Wait 10 seconds for logs to be written to Cloud service. + await delay(10 * 1000); + + // Make sure the log was written to Cloud Logging. + const log = logging.log(`samples_express_${APP_LOG_SUFFIX}`); + const entries = (await log.getEntries({pageSize: 1}))[0]; + assert.strictEqual(entries.length, 1); + const entry = entries[0]; + assert.strictEqual('this is an info log message', entry.data.message); + // Ensure that a functional logger ws configured with the sample: + assert.ok(entry.metadata.trace, 'should have a trace property'); + assert.match(entry.metadata.trace, /projects\/.*\/traces\/.*/); + }); +}); diff --git a/handwritten/nodejs-logging-bunyan/samples/system-test/quickstart.test.js b/handwritten/nodejs-logging-bunyan/samples/system-test/quickstart.test.js new file mode 100644 index 00000000000..fc1d4330939 --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/samples/system-test/quickstart.test.js @@ -0,0 +1,31 @@ +// Copyright 2017 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +const path = require('path'); +const {assert} = require('chai'); +const {describe, it} = require('mocha'); +const cp = require('child_process'); + +const execSync = cmd => cp.execSync(cmd, {encoding: 'utf-8'}); + +const cwd = path.join(__dirname, '..'); + +describe('quickstart samples', () => { + it('should write using bunyan', async () => { + const stdout = execSync('node quickstart.js', {cwd}); + assert.match(stdout, /99%/); + }); +}); diff --git a/handwritten/nodejs-logging-bunyan/src/index.ts b/handwritten/nodejs-logging-bunyan/src/index.ts new file mode 100644 index 00000000000..3269d67ecd0 --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/src/index.ts @@ -0,0 +1,515 @@ +/*! + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {Writable} from 'stream'; +import * as express from './middleware/express'; +import { + setInstrumentationStatus, + createDiagnosticEntry, +} from '@google-cloud/logging/build/src/utils/instrumentation'; + +// Export the express middleware as 'express'. +export {express}; + +import { + Logging, + detectServiceContext, + Log, + LogSync, + Entry, +} from '@google-cloud/logging'; + +import * as types from './types/core'; + +import {ApiResponseCallback} from '@google-cloud/logging/build/src/log'; +import {LogSeverityFunctions} from '@google-cloud/logging/build/src/utils/log-common'; +import {LogSyncOptions} from '@google-cloud/logging/build/src/log-sync'; + +// Map of Stackdriver logging levels. +const BUNYAN_TO_STACKDRIVER: Map = new Map([ + [60, 'CRITICAL'], + [50, 'ERROR'], + [40, 'WARNING'], + [30, 'INFO'], + [20, 'DEBUG'], + [10, 'DEBUG'], +]); + +/** + * Key to use in the Bunyan payload to allow users to indicate a trace for the + * request, and to store as an intermediate value on the log entry before it + * gets written to the Cloud Logging logging API. + */ +export const LOGGING_TRACE_KEY = 'logging.googleapis.com/trace'; + +/** + * Key to use in the Bunyan payload to allow users to indicate a spanId for the + * request, and to store as an intermediate value on the log entry before it + * gets written to the Cloud logging API. + */ +export const LOGGING_SPAN_KEY = 'logging.googleapis.com/spanId'; + +/** + * Key to use in the Bunyan payload to allow users to indicate a traceSampled + * flag for the request, and to store as an intermediate value on the log entry + * before it gets written to the Cloud logging API. + */ +export const LOGGING_SAMPLED_KEY = 'logging.googleapis.com/trace_sampled'; + +/** + * Default library version to be used + * Using release-please annotations to update DEFAULT_INSTRUMENTATION_VERSION with latest version. + * See https://github.com/googleapis/release-please/blob/main/docs/customizing.md#updating-arbitrary-files + */ +export const NODEJS_BUNYAN_DEFAULT_LIBRARY_VERSION = '5.1.0'; // {x-release-please-version} + +/** + * Gets the current fully qualified trace ID when available from the + * @google-cloud/trace-agent library in the LogEntry.trace field format of: + * "projects/[PROJECT-ID]/traces/[TRACE-ID]". + */ +export function getCurrentTraceFromAgent() { + const agent = global._google_trace_agent; + if (!agent || !agent.getCurrentContextId || !agent.getWriterProjectId) { + return null; + } + + const traceId = agent.getCurrentContextId(); + if (!traceId) { + return null; + } + + const traceProjectId = agent.getWriterProjectId(); + if (!traceProjectId) { + return null; + } + + return `projects/${traceProjectId}/traces/${traceId}`; +} + +/** + * This module provides support for streaming your Bunyan logs to + * [Stackdriver Logging](https://cloud.google.com/logging). + * + * @class + * + * @param {object} [options] + * @param {string} [options.logName] The name of the log that will receive + * messages written to this bunyan stream. Default: `bunyan_Log`. + * @param {object} [options.resource] The monitored resource that the log + * stream corresponds to. On Google Cloud Platform, this is detected + * automatically, but you may optionally specify a specific monitored + * resource. For more information, see the + * [official documentation]{@link + * https://cloud.google.com/monitoring/api/ref_v3/rpc/google.api#google.api.MonitoredResource} + * + * @param {object} [options.serviceContext] For logged errors, we provide this + * as the service context. For more information see + * [this guide]{@link + * https://cloud.google.com/error-reporting/docs/formatting-error-messages} and + * the [official documentation]{@link + * https://cloud.google.com/error-reporting/reference/rest/v1beta1/ServiceContext}. + * @param {string} [options.serviceContext.service] An identifier of the + * service, such as the name of the executable, job, or Google App Engine + * service name. + * @param {string} [options.serviceContext.version] Represents the version of + * the service. + * @param {string} [options.projectId] The project ID from the Google Cloud + * Console, e.g. 'grape-spaceship-123'. We will also check the environment + * variable `GCLOUD_PROJECT` for your project ID. If your app is running in + * an environment which supports {@link + * https://cloud.google.com/docs/authentication/production#providing_credentials_to_your_application + * Application Default Credentials}, your project ID will be detected + * automatically. + * @param {string} [options.keyFilename] Full path to the a .json, .pem, or .p12 + * key downloaded from the Google Cloud Console. If you provide a path + * to a JSON file, the `projectId` option above is not necessary. NOTE: .pem + * and .p12 require you to specify the `email` option as well. + * @param {string} [options.email] Account email address. Required when using a + * .pem or .p12 keyFilename. + * @param {object} [options.credentials] Credentials object. + * @param {string} [options.credentials.client_email] + * @param {string} [options.credentials.private_key] + * @param {boolean} [options.autoRetry=true] Automatically retry requests if the + * response is related to rate limits or certain intermittent server errors. + * We will exponentially backoff subsequent requests by default. + * @param {number} [options.maxRetries=3] Maximum number of automatic retries + * attempted before returning the error. + * @param {constructor} [options.promise] Custom promise module to use instead + * of native Promises. + * @param {constructor} [options.promise] Custom promise module to use instead + * of native Promises. + * @param {number} [options.maxEntrySize] Max size limit of a log entry. + * @param {string[]} [options.jsonFieldsToTruncate] A list of JSON properties at the given full path to be truncated. + * @example Import the client library + * ``` + * const {LoggingBunyan} = require('@google-cloud/logging-bunyan'); + * + * ``` + * @example Create a client that uses Application Default Credentials (ADC): + * ``` + * const loggingBunyan = new + * LoggingBunyan(); + * + * ``` + * @example Create a client with explicit credentials: + * ``` + * const loggingBunyan = new LoggingBunyan({ + * projectId: 'your-project-id', + * keyFilename: '/path/to/keyfile.json' + * }); + * + * ``` + * @example include:samples/quickstart.js + * region_tag:logging_bunyan_quickstart + * Full quickstart example: + * + */ +export class LoggingBunyan extends Writable { + private logName: string; + private resource: types.MonitoredResource | undefined; + private serviceContext?: types.ServiceContext; + private defaultCallback?: ApiResponseCallback; + cloudLog: LogSeverityFunctions; + redirectToStdout: boolean; + + constructor(options?: types.Options) { + options = options || {}; + super({objectMode: true}); + this.logName = options.logName || 'bunyan_log'; + this.resource = options.resource; + this.serviceContext = options.serviceContext; + this.defaultCallback = options.defaultCallback; + this.redirectToStdout = options.redirectToStdout ?? false; + if (!this.redirectToStdout) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + this.cloudLog = new Logging(options).log(this.logName, { + removeCircular: true, + // See: https://cloud.google.com/logging/quotas, a log size of + // 250,000 has been chosen to keep us comfortably within the + // 256,000 limit. + maxEntrySize: options.maxEntrySize || 250000, + jsonFieldsToTruncate: options.jsonFieldsToTruncate, + }); + } else { + const logSyncOptions: LogSyncOptions = { + useMessageField: options.useMessageField ?? true, + }; + this.cloudLog = new Logging(options).logSync( + this.logName, + undefined, + logSyncOptions + ); + } + + // serviceContext.service is required by the Error Reporting + // API. Without it, errors that are logged with level 'error' + // or higher will not be displayed in the Error Reporting + // console. + // + // As a result, if serviceContext is specified, it is required + // that serviceContext.service is specified. + if (this.serviceContext && !this.serviceContext.service) { + throw new Error( + "If 'serviceContext' is specified then " + + "'serviceContext.service' is required." + ); + } + + /* Asynchrnously attempt to discover the service context. */ + if (!this.serviceContext) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + detectServiceContext((this.cloudLog as any).logging.auth).then( + serviceContext => { + this.serviceContext = serviceContext!; + }, + () => { + /* swallow any errors. */ + } + ); + } + } + + /** + * Convenience method that Builds a bunyan stream object that you can put in + * the bunyan streams list. + */ + stream(level: types.LogLevel): types.StreamResponse { + return {level, type: 'raw', stream: this as Writable}; + } + + /** + * Format a bunyan record into a Stackdriver log entry. + */ + private formatEntry_(record: string | types.BunyanLogRecord) { + if (typeof record === 'string') { + throw new Error( + '@google-cloud/logging-bunyan only works as a raw bunyan stream type.' + ); + } + // Stackdriver Log Viewer picks up the summary line from the 'message' field + // of the payload. Unless the user has provided a 'message' property also, + // move the 'msg' to 'message'. + if (!record.message) { + // If this is an error, report the full stack trace. This allows + // Stackdriver Error Reporting to pick up errors automatically (for + // severity 'error' or higher). In this case we leave the 'msg' property + // intact. + // https://cloud.google.com/error-reporting/docs/formatting-error-messages + // + if (record.err && record.err.stack) { + record.message = record.err.stack; + record.serviceContext = this.serviceContext; + } else if (record.msg) { + // Simply rename `msg` to `message`. + record.message = record.msg; + delete record.msg; + } + } + + const entryMetadata: types.StackdriverEntryMetadata = { + resource: this.resource, + timestamp: record.time, + severity: BUNYAN_TO_STACKDRIVER.get(Number(record.level)), + }; + + // If the record contains a httpRequest property, provide it on the entry + // metadata. This allows Stackdriver to use request log formatting. + // https://cloud.google.com/logging/docs/reference/v2/rpc/google.logging.type#google.logging.type.HttpRequest + // Note that the httpRequest field must properly validate as a HttpRequest + // proto message, or the log entry would be rejected by the API. We do no + // validation here. + if (record.httpRequest) { + entryMetadata.httpRequest = record.httpRequest; + delete record.httpRequest; + } + + // If the record contains a labels property, promote it to the entry + // metadata. + // https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry + const proper = LoggingBunyan.properLabels(record.labels); + if (record.labels && proper) { + entryMetadata.labels = record.labels; + delete record.labels; + } + + if (record[LOGGING_TRACE_KEY]) { + entryMetadata.trace = record[LOGGING_TRACE_KEY]; + delete record[LOGGING_TRACE_KEY]; + } + + if (record[LOGGING_SPAN_KEY]) { + entryMetadata.spanId = record[LOGGING_SPAN_KEY]; + delete record[LOGGING_SPAN_KEY]; + } + + if (LOGGING_SAMPLED_KEY in record) { + entryMetadata.traceSampled = record[LOGGING_SAMPLED_KEY]; + delete record[LOGGING_SAMPLED_KEY]; + } + + return ( + ( + this.redirectToStdout + ? (this.cloudLog as LogSync) + : (this.cloudLog as Log) + ) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .entry(entryMetadata as any, record) + ); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + static properLabels(labels: any) { + if (typeof labels !== 'object') return false; + for (const prop in labels) { + if (typeof labels[prop] !== 'string') { + return false; + } + } + return true; + } + + /** + * Intercept log entries as they are written so we can attempt to add the + * trace ID in the same continuation as the function that wrote the log, + * because the trace agent currently uses continuation local storage for the + * trace context. + * + * By the time the Writable stream buffer gets flushed and _write gets called + * we may well be in a different continuation. + */ + write(record: types.BunyanLogRecord, callback?: Function): boolean; + write( + record: types.BunyanLogRecord, + encoding?: string, + callback?: Function + ): boolean; + // Writable.write used 'any' in function signature. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + write(...args: any[]): boolean { + let record = args[0]; + let encoding: string | null = null; + type Callback = (error: Error | null | undefined) => void; + let callback: Callback | string; + if (typeof args[1] === 'string') { + encoding = args[1]; + callback = args[2]; + } else { + callback = args[1]; + } + record = Object.assign({}, record); + if (!record[LOGGING_TRACE_KEY]) { + const trace = getCurrentTraceFromAgent(); + if (trace) { + record[LOGGING_TRACE_KEY] = trace; + } + } + if (encoding !== null) { + return super.write.call( + this, + record, + encoding as BufferEncoding, + callback as Callback + ); + } else { + return super.write.call(this, record, callback as BufferEncoding); + } + } + + /** + * Relay a log entry to the logging agent. This is called by bunyan through + * Writable#write. + */ + _write(record: types.BunyanLogRecord, encoding: string, callback: Function) { + const entry = this.formatEntry_(record); + this._writeCall(entry, callback); + } + + /** + * Relay an array of log entries to the logging agent. This is called by + * bunyan through Writable#write. + */ + // Writable._write used 'any' in function signature. + _writev( + chunks: Array<{ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + chunk: any; + encoding: string; + }>, + callback: Function + ) { + const entries = chunks.map( + (request: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + chunk: any; + encoding: string; + }) => { + return this.formatEntry_(request.chunk); + } + ); + this._writeCall(entries, callback); + } + + /** + * Creates a combined callback which calls the this.defaultCallback and the Writable.write supplied callback + * @param callback The callback function provided by Writable + * @returns Combined callback which executes both, this.defaultCallback and one supplied by Writable.write + */ + generateCallback(callback: Function): ApiResponseCallback { + // Make sure that both callbacks are called in case if provided + const newCallback: ApiResponseCallback = ( + err: Error | null, + apiResponse?: {} + ) => { + if (callback) { + callback(err, apiResponse); + } + if (this.defaultCallback) { + this.defaultCallback(err, apiResponse); + } + }; + return newCallback; + } + + /** + * A helper function to make a write call + * @param entries The entries to be written + * @param callback The callback supplied by Writable.write + */ + _writeCall(entries: Entry | Entry[], callback: Function) { + // First create instrumentation record if it is never written before + const alreadyWritten = setInstrumentationStatus(true); + if (!alreadyWritten) { + let instrumentationEntry = createDiagnosticEntry( + 'nodejs-bunyan', + this.getNodejsLibraryVersion() + ); + // Update instrumentation record resource and logName + instrumentationEntry.metadata.resource = this.resource; + instrumentationEntry.metadata.severity = 'INFO'; + instrumentationEntry = ( + this.redirectToStdout + ? (this.cloudLog as LogSync) + : (this.cloudLog as Log) + ).entry(instrumentationEntry.metadata, instrumentationEntry.data); + entries = Array.isArray(entries) ? entries : [entries]; + entries.push(instrumentationEntry); + } + if (this.redirectToStdout) { + (this.cloudLog as LogSync).write(entries); + // The LogSync class does not supports callback. However if callback provided and + // if this.defaultCallback exists, we should call it + this.generateCallback(callback)(null, undefined); + } else { + (this.cloudLog as Log).write(entries, this.generateCallback(callback)); + } + } + + /** + * Method used to retrieve the current logging-bunyan library version stored in NODEJS_BUNYAN_DEFAULT_LIBRARY_VERSION + * @returns The version of this library + */ + getNodejsLibraryVersion() { + return NODEJS_BUNYAN_DEFAULT_LIBRARY_VERSION; + } +} + +module.exports.BUNYAN_TO_STACKDRIVER = BUNYAN_TO_STACKDRIVER; + +/** + * Value: `logging.googleapis.com/trace` + * + * @name LoggingBunyan.LOGGING_TRACE_KEY + * @type {string} + */ +module.exports.LOGGING_TRACE_KEY = LOGGING_TRACE_KEY; + +/** + * Value: `logging.googleapis.com/spanId` + * + * @name LoggingBunyan.LOGGING_SPAN_KEY + * @type {string} + */ +module.exports.LOGGING_SPAN_KEY = LOGGING_SPAN_KEY; + +/** + * Value: `logging.googleapis.com/trace_sampled` + * + * @name LoggingBunyan.LOGGING_SAMPLED_KEY + * @type {string} + */ +module.exports.LOGGING_SAMPLED_KEY = LOGGING_SAMPLED_KEY; diff --git a/handwritten/nodejs-logging-bunyan/src/middleware/express.ts b/handwritten/nodejs-logging-bunyan/src/middleware/express.ts new file mode 100644 index 00000000000..caf5d31789d --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/src/middleware/express.ts @@ -0,0 +1,127 @@ +/*! + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + HttpRequest, + middleware as commonMiddleware, +} from '@google-cloud/logging'; +import * as bunyan from 'bunyan'; +import {GCPEnv} from 'google-auth-library'; + +import { + LOGGING_TRACE_KEY, + LOGGING_SPAN_KEY, + LOGGING_SAMPLED_KEY, + LoggingBunyan, +} from '../index'; +import * as types from '../types/core'; + +export const APP_LOG_SUFFIX = 'applog'; + +// @types/bunyan doesn't export Logger. Access it via ReturnType on +// createLogger. +export type Logger = ReturnType; + +export interface MiddlewareOptions extends types.Options { + level?: types.LogLevel; + // Enable the same behavior when running in Cloud Run as when running in Cloud Functions or App Engine to not create a request log entry + // that all the request-specific logs ("app logs") will nest under. GAE and GCF generate the parent request log automatically. Cloud Run + // also does this, so it is best to also skip this entry for Cloud Run. But that is considered a breaking change so this config option is + // introduced to enable this same behavior for Cloud Run. This might become the default behavior in a future major version. + skipParentEntryForCloudRun?: boolean; +} + +export interface MiddlewareReturnType { + logger: Logger; + mw: ReturnType; +} + +/** + * Express middleware + */ +export async function middleware( + options?: MiddlewareOptions +): Promise { + const defaultOptions = {logName: 'bunyan_log', level: 'info'}; + options = Object.assign({}, defaultOptions, options); + + const loggingBunyanApp = new LoggingBunyan( + Object.assign({}, options, { + // For request bundling to work, the parent (request) and child (app) logs + // need to have distinct names. For exact requirements see: + // https://cloud.google.com/appengine/articles/logging#linking_app_logs_and_requests + logName: `${options.logName}_${APP_LOG_SUFFIX}`, + }) + ); + const logger = bunyan.createLogger({ + name: `${options.logName}_${APP_LOG_SUFFIX}`, + streams: [loggingBunyanApp.stream(options.level as types.LogLevel)], + }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const auth = (loggingBunyanApp.cloudLog as any).logging.auth; + const [env, projectId] = await Promise.all([ + auth.getEnv(), + auth.getProjectId(), + ]); + + // Unless we are running on Google App Engine or Cloud Functions, generate a + // parent request log entry that all the request-specific logs ("app logs") + // will nest under. GAE and GCF generate the parent request logs + // automatically. + // Cloud Run also generates the parent request log automatically, but skipping + // the parent request entry has to be explicity enabled until the next major + // release in which we can change the default behavior. + let emitRequestLog; + if ( + env !== GCPEnv.APP_ENGINE && + env !== GCPEnv.CLOUD_FUNCTIONS && + (env !== GCPEnv.CLOUD_RUN || !options.skipParentEntryForCloudRun) + ) { + const loggingBunyanReq = new LoggingBunyan(options); + const requestLogger = bunyan.createLogger({ + name: options.logName!, + streams: [loggingBunyanReq.stream(options.level as types.LogLevel)], + }); + emitRequestLog = ( + httpRequest: HttpRequest, + trace: string, + span?: string, + sampled?: boolean + ) => { + requestLogger.info({ + [LOGGING_TRACE_KEY]: trace, + [LOGGING_SPAN_KEY]: span, + [LOGGING_SAMPLED_KEY]: sampled, + httpRequest, + }); + }; + } + + return { + logger, + mw: commonMiddleware.express.makeMiddleware( + projectId, + makeChildLogger, + emitRequestLog + ), + }; + + function makeChildLogger(trace: string, span?: string) { + return logger.child( + {[LOGGING_TRACE_KEY]: trace, [LOGGING_SPAN_KEY]: span}, + true /* simple child */ + ); + } +} diff --git a/handwritten/nodejs-logging-bunyan/src/types/core.d.ts b/handwritten/nodejs-logging-bunyan/src/types/core.d.ts new file mode 100644 index 00000000000..1b2dd2e36cf --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/src/types/core.d.ts @@ -0,0 +1,323 @@ +/*! + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {ApiResponseCallback} from '@google-cloud/logging/build/src/log'; + +export interface Options { + /** + * The name of the log that will receive messages written to this bunyan + * stream. Default: `bunyan_Log`. + */ + logName?: string; + /** + * The monitored resource that the log stream corresponds to. On Google Cloud + * Platform, this is detected automatically, but you may optionally specify a + * specific monitored resource. For more information, see the + * [official documentation]{@link https://cloud.google.com/logging/docs/api/reference/rest/v2/MonitoredResource} + */ + resource?: MonitoredResource; + /** + * For logged errors, we provide this as the service context. For more + * information see [this guide]{@link https://cloud.google.com/error-reporting/docs/formatting-error-messages} + * and the [official documentation]{@link https://cloud.google.com/error-reporting/reference/rest/v1beta1/ServiceContext}. + */ + serviceContext?: ServiceContext; + /** + * The project ID from the Google Cloud + * Console, e.g. 'grape-spaceship-123'. We will also check the environment + * variable `GCLOUD_PROJECT` for your project ID. If your app is running in + * an environment which supports {@link https://cloud.google.com/docs/authentication/production#providing_credentials_to_your_application Application Default Credentials}, + * your project ID will be detected automatically. + */ + projectId?: string; + /** + * Full path to the a .json, .pem, or .p12 key downloaded from the Google + * Cloud Console. If you provide a path to a JSON file, the `projectId` option + * above is not necessary. NOTE: .pem and .p12 require you to specify the + * `email` option as well. + */ + keyFilename?: string; + /** + * Account email address. Required when using a .pem or .p12 keyFilename. + */ + email?: string; + /** + * Credentials object. + */ + credentials?: Credentials; + /** + * Automatically retry requests if the response is related to rate limits or + * certain intermittent server errors. We will exponentially backoff + * subsequent requests by default. + */ + autoRetry?: boolean; + /** + * Maximum number of automatic retries attempted before returning the error. + */ + maxRetries?: number; + /** + * Custom promise module to use instead of native Promises. + */ + // TODO: address the correct type of promise. + promise?: {}; + /** + * The host name of the service to send requests. + * Defaults to `logging.googleapis.com`. + */ + apiEndpoint?: string; + /** + * An attempt will be made to truncate messages larger than maxEntrySize. + * Please note that this parameter is ignored when redirectToStdout is set. + */ + maxEntrySize?: number; + /** + * A list of JSON properties at the given full path to be truncated. + * Received values will be prepended to predefined list in the order received and duplicates discarded. + */ + jsonFieldsToTruncate?: string[]; + // A default global callback to be used for {@link LoggingBunyan} write calls + // when callback is not supplied by caller in function parameters + defaultCallback?: ApiResponseCallback; + /** + * Boolen flag that opts-in redirecting the output to STDOUT instead of ingesting logs to Cloud + * Logging using Logging API. Defaults to {@code false}. Redirecting logs can be used in + * Google Cloud environments with installed logging agent to delegate log ingestions to the + * agent. Redirected logs are formatted as one line Json string following the structured logging guidelines. + */ + redirectToStdout?: boolean; + + /** + * Boolean flag indicating if "message" field should be used to store structured, + * non-text data inside jsonPayload field. This flag applies only when {@link Options#redirectToStdout} is set. + * By default this value is true + */ + useMessageField?: boolean; +} + +export interface MonitoredResource { + type?: string; + labels?: {[key: string]: string}; +} + +export interface ServiceContext { + /** + * An identifier of the service, such as the name of the executable, job, or + * Google App Engine service name. + */ + service?: string; + /** + * Represents the version of the service. + */ + version?: string; +} + +export interface StackdriverLog { + critical: ( + entry: StackdriverEntry | StackdriverEntry[], + options?: {}, + callback?: (err: Error, apiResponse: {}) => void + ) => Promise; + debug: ( + entry: StackdriverEntry | StackdriverEntry[], + options?: {}, + callback?: (err: Error, apiResponse: {}) => void + ) => Promise; + emergency: ( + entry: StackdriverEntry | StackdriverEntry[], + options?: {}, + callback?: (err: Error, apiResponse: {}) => void + ) => Promise; + error: ( + entry: StackdriverEntry | StackdriverEntry[], + options?: {}, + callback?: (err: Error, apiResponse: {}) => void + ) => Promise; + info: ( + entry: StackdriverEntry | StackdriverEntry[], + options?: {}, + callback?: (err: Error, apiResponse: {}) => void + ) => Promise; + notice: ( + entry: StackdriverEntry | StackdriverEntry[], + options?: {}, + callback?: (err: Error, apiResponse: {}) => void + ) => Promise; + warning: ( + entry: StackdriverEntry | StackdriverEntry[], + options?: {}, + callback?: (err: Error, apiResponse: {}) => void + ) => Promise; + write: ( + entry: StackdriverEntry | StackdriverEntry[], + options?: {}, + callback?: (err: Error, apiResponse: {}) => void + ) => Promise; + alert: ( + entry: StackdriverEntry | StackdriverEntry[], + options?: {}, + callback?: (err: Error, apiResponse: {}) => void + ) => Promise; + entry: (metadata: {}, data: {} | string) => StackdriverEntry; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + logging: any; +} + +export interface Credentials { + client_email?: string; + private_key?: string; +} + +export interface StackdriverEntryMetadata { + resource?: MonitoredResource; + timestamp?: Date; + severity?: string; // figure out the correct type later + httpRequest?: HttpRequest; + labels?: {}; + trace?: {}; + spanId?: {}; + traceSampled?: {}; +} + +export interface StackdriverLog { + critical: ( + entry: StackdriverEntry | StackdriverEntry[], + options?: {}, + callback?: (err: Error, apiResponse: {}) => void + ) => Promise; + debug: ( + entry: StackdriverEntry | StackdriverEntry[], + options?: {}, + callback?: (err: Error, apiResponse: {}) => void + ) => Promise; + emergency: ( + entry: StackdriverEntry | StackdriverEntry[], + options?: {}, + callback?: (err: Error, apiResponse: {}) => void + ) => Promise; + error: ( + entry: StackdriverEntry | StackdriverEntry[], + options?: {}, + callback?: (err: Error, apiResponse: {}) => void + ) => Promise; + info: ( + entry: StackdriverEntry | StackdriverEntry[], + options?: {}, + callback?: (err: Error, apiResponse: {}) => void + ) => Promise; + notice: ( + entry: StackdriverEntry | StackdriverEntry[], + options?: {}, + callback?: (err: Error, apiResponse: {}) => void + ) => Promise; + warning: ( + entry: StackdriverEntry | StackdriverEntry[], + options?: {}, + callback?: (err: Error, apiResponse: {}) => void + ) => Promise; + write: ( + entry: StackdriverEntry | StackdriverEntry[], + options?: {}, + callback?: (err: Error, apiResponse: {}) => void + ) => Promise; + alert: ( + entry: StackdriverEntry | StackdriverEntry[], + options?: {}, + callback?: (err: Error, apiResponse: {}) => void + ) => Promise; + entry: (metadata: {}, data: {} | string) => StackdriverEntry; +} + +export interface StackdriverLogging { + Entry?: StackdriverEntry; + Log?: StackdriverLog; + Logging?: StackdriverLogging; + entry?: ( + resource?: MonitoredResource, + data?: {message: string} | string + ) => StackdriverEntry; + // define additional properties and methods. +} + +export interface StackdriverEntry { + constructor: ( + metadata?: StackdriverEntryMetadata, + data?: {message: string} | string + ) => StackdriverEntry; + data?: StackdriverData | string; + metadata?: StackdriverEntryMetadata; +} + +export interface StackdriverData { + serviceContext?: ServiceContext; + message?: string; + metadata?: Metadata; + pid?: string; + test?: {circular?: string}; +} + +export interface Metadata { + stack?: string; + httpRequest?: HttpRequest; +} + +type LogWriteResponse = {}[]; + +export interface HttpRequest { + requestMethod?: string; + requestUrl?: string; + requestSize?: number; + status?: number; + responseSize?: number; + userAgent?: string; + remoteIp?: string; + serverIp?: string; + referer?: string; + latency?: string | {seconds: number; nanos: number}; + cacheLookup?: boolean; + cacheHit?: boolean; + cacheValidatedWithOriginServer?: boolean; + cacheFillBytes?: number; + protocol?: string; +} + +export interface BunyanLogRecord { + message?: string; + msg?: string; + err?: Error; + serviceContext?: ServiceContext; + level?: string; + time?: Date; + httpRequest?: HttpRequest; + labels?: {}; + // And arbitrary other properties. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [key: string]: any; +} + +export type LogLevel = + | 'trace' + | 'debug' + | 'info' + | 'warn' + | 'error' + | 'fatal' + | number; + +export interface StreamResponse { + level: LogLevel; + type: string; + stream: NodeJS.WritableStream; +} diff --git a/handwritten/nodejs-logging-bunyan/src/types/global.d.ts b/handwritten/nodejs-logging-bunyan/src/types/global.d.ts new file mode 100644 index 00000000000..656a0be44f1 --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/src/types/global.d.ts @@ -0,0 +1,22 @@ +/*! + * Copyright 2018 Google LLC. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export {}; + +declare global { + // eslint-disable-next-line + var _google_trace_agent: any; +} diff --git a/handwritten/nodejs-logging-bunyan/system-test/errors-transport.ts b/handwritten/nodejs-logging-bunyan/system-test/errors-transport.ts new file mode 100644 index 00000000000..6445c561792 --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/system-test/errors-transport.ts @@ -0,0 +1,134 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as common from '@google-cloud/common'; +import delay from 'delay'; + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const packageJson = require('../../package.json'); + +export interface ServiceContext { + service: string; + version: string; + resourceType: string; +} + +export interface ErrorEvent { + eventTime: string; + serviceContext: ServiceContext; + message: string; + // other fields not used in the tests have been omitted +} + +export interface ErrorGroupStats { + group: {groupId: string}; + affectedServices: ServiceContext[]; + representative: ErrorEvent; + count: string; + // other fields not used in the tests have been omitted +} + +export interface ApiResponse { + body: {errorGroupStats: ErrorGroupStats[]; errorEvents: ErrorEvent[]}; +} + +/* @const {String} Base Error Reporting API */ +const API = 'https://clouderrorreporting.googleapis.com/v1beta1/projects'; + +const ONE_HOUR_API = 'timeRange.period=PERIOD_1_HOUR'; + +export class ErrorsApiTransport extends common.Service { + constructor() { + super({ + baseUrl: 'https://clouderrorreporting.googleapis.com/v1beta1', + apiEndpoint: 'clouderrorreporting.googleapis.com', + scopes: ['https://www.googleapis.com/auth/cloud-platform'], + packageJson, + }); + } + + async request(options: common.DecorateRequestOptions) { + return new Promise((resolve, reject) => { + super.request(options, (err, _, res) => { + return err ? reject(err) : resolve(res as common.ResponseBody); + }); + }); + } + + async getAllGroups(): Promise { + const projectId = await this.getProjectId(); + const options = { + uri: [API, projectId, 'groupStats?' + ONE_HOUR_API].join('/'), + method: 'GET', + }; + const response = await this.request(options); + return response.body.errorGroupStats || []; + } + + async getGroupEvents(groupId: string): Promise { + const projectId = await this.getProjectId(); + const options = { + uri: [ + API, + projectId, + 'events?groupId=' + groupId + '&pageSize=10&' + ONE_HOUR_API, + ].join('/'), + method: 'GET', + }; + + const response = await this.request(options); + return response.body.errorEvents || []; + } + + async pollForNewEvents( + service: string, + time: number, + timeout: number + ): Promise { + const timeLimit = Date.now() + timeout; + let groupId; + // wait for a group + while (Date.now() < timeLimit) { + await delay(1000); + + if (!groupId) { + const groups = await this.getAllGroups(); + if (!groups.length) continue; + // find an error group that matches the service + groups.forEach(group => { + const match = group.affectedServices.find( + context => context.service === service + ); + if (match) { + groupId = group.group.groupId; + } + }); + } + + // didnt find an error reporting group matching the service. + if (!groupId) continue; + + const events = await this.getGroupEvents(groupId); + const filteredEvents = events.filter( + event => + event.serviceContext.service === service && + new Date(event.eventTime).getTime() >= time + ); + if (filteredEvents.length) { + return filteredEvents; + } + } + return []; + } +} diff --git a/handwritten/nodejs-logging-bunyan/system-test/logging-bunyan.ts b/handwritten/nodejs-logging-bunyan/system-test/logging-bunyan.ts new file mode 100644 index 00000000000..ec93e734352 --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/system-test/logging-bunyan.ts @@ -0,0 +1,301 @@ +/*! + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as assert from 'assert'; +import {describe, it} from 'mocha'; +import * as bunyan from 'bunyan'; +import * as uuid from 'uuid'; +import * as types from '../src/types/core'; +import {ErrorsApiTransport} from './errors-transport'; +import {Logging, LogSync} from '@google-cloud/logging'; + +const logging = new Logging(); +import {LoggingBunyan} from '../src/index'; +import delay from 'delay'; +import * as instrumentation from '@google-cloud/logging/build/src/utils/instrumentation'; + +const WRITE_CONSISTENCY_DELAY_MS = 90000; +const MESSAGE = 'Diagnostic test'; + +const UUID = uuid.v4(); +function logName(name: string) { + return `${UUID}_${name}`; +} + +describe('LoggingBunyan', function () { + this.timeout(WRITE_CONSISTENCY_DELAY_MS); + + const SERVICE = `logging-bunyan-system-test-${UUID}`; + const LOG_NAME = logName('logging-bunyan-system-test'); + const loggingBunyan = new LoggingBunyan({ + logName: LOG_NAME, + serviceContext: {service: SERVICE, version: 'none'}, + }); + const logger = bunyan.createLogger({ + name: 'google-cloud-node-system-test', + streams: [loggingBunyan.stream('info')], + }); + + it('should create LoggingBunyan with LogSync', () => { + const loggingBunyan = new LoggingBunyan({ + logName: LOG_NAME, + redirectToStdout: true, + }); + assert.ok(loggingBunyan.cloudLog instanceof LogSync); + }); + + it('should create LoggingBunyan with LogSync and useMessageField is off', () => { + const loggingBunyan = new LoggingBunyan({ + logName: LOG_NAME, + redirectToStdout: true, + useMessageField: false, + }); + assert.ok(loggingBunyan.cloudLog instanceof LogSync); + assert.ok(loggingBunyan.cloudLog.useMessageField_ === false); + }); + + it('should write diagnostic entry', async () => { + instrumentation.setInstrumentationStatus(false); + const start = Date.now(); + logger.info(MESSAGE); + const entries = await pollLogs( + LOG_NAME, + start, + 2, + WRITE_CONSISTENCY_DELAY_MS + ); + assert.strictEqual(entries.length, 2); + let isDiagnosticPresent = false; + entries.forEach(entry => { + assert.ok(entry.data); + if ( + Object.prototype.hasOwnProperty.call( + entry.data, + instrumentation.DIAGNOSTIC_INFO_KEY + ) + ) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const info = (entry.data as any)[instrumentation.DIAGNOSTIC_INFO_KEY][ + instrumentation.INSTRUMENTATION_SOURCE_KEY + ]; + assert.equal(info[0].name, 'nodejs-bunyan'); + assert.ok(info[0].version.includes('.')); + assert.equal(info[1].name, 'nodejs'); + assert.ok(info[1].version.includes('.')); + isDiagnosticPresent = true; + } else { + const data = entry.data as {message: string}; + assert.ok(data.message.includes(MESSAGE)); + } + }); + assert.ok(isDiagnosticPresent); + }); + + it('should properly write log entries', async function () { + this.retries(3); + const timestamp = new Date(); + const start = Date.now(); + + // Type of circular.circular cannot be determined.. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const circular: {circular?: any} = {}; + circular.circular = circular; + + const testData = [ + { + args: ['first'], + level: 'info', + verify: (entry: types.StackdriverEntry) => { + assert.strictEqual( + (entry.data as types.StackdriverData).message, + 'first' + ); + assert.strictEqual( + (entry.data as types.StackdriverData).pid, + process.pid + ); + }, + }, + + { + args: [new Error('second')], + level: 'error', + verify: (entry: types.StackdriverEntry) => { + assert( + ((entry.data as types.StackdriverData).message as string).includes( + 'Error: second' + ) + ); + assert.strictEqual( + (entry.data as types.StackdriverData).pid, + process.pid + ); + }, + }, + + { + args: [ + { + test: circular, + }, + 'third', + ], + level: 'info', + verify: (entry: types.StackdriverEntry) => { + assert.strictEqual( + (entry.data as types.StackdriverData).message, + 'third' + ); + assert.strictEqual( + (entry.data as types.StackdriverData).pid, + process.pid + ); + assert.deepStrictEqual((entry.data as types.StackdriverData).test, { + circular: '[Circular]', + }); + }, + }, + ]; + + const earliest = { + args: [ + { + time: timestamp, + }, + 'earliest', + ], + level: 'info', + verify: (entry: types.StackdriverEntry) => { + assert.strictEqual( + (entry.data as types.StackdriverData).message, + 'earliest' + ); + assert.strictEqual( + (entry.data as types.StackdriverData).pid, + process.pid + ); + assert.strictEqual( + ( + (entry.metadata as types.StackdriverEntryMetadata).timestamp as Date + ).toString(), + timestamp.toString() + ); + }, + }; + + // Forcibly insert a delay to cause 'third' to have a deterministically + // earlier timestamp. + await delay(10); + + testData.forEach(test => { + // logger does not have index signature. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (logger as any)[test.level].apply(logger, test.args); + }); + // `earliest` is sent last, but it should show up as the earliest entry. + // logger does not have index signature. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (logger as any)[earliest.level].apply(logger, earliest.args); + // insert into list as the earliest entry. + // TODO: identify the correct type for testData and earliest + // eslint-disable-next-line @typescript-eslint/no-explicit-any + testData.unshift(earliest as any); + + const entries = await pollLogs( + LOG_NAME, + start, + testData.length, + WRITE_CONSISTENCY_DELAY_MS + ); + assert.strictEqual(entries.length, testData.length); + entries.reverse().forEach((entry, index) => { + const test = testData[index]; + test.verify(entry); + }); + }); + + describe('ErrorReporting', () => { + const ERROR_REPORTING_POLL_TIMEOUT = WRITE_CONSISTENCY_DELAY_MS; + const errorsTransport = new ErrorsApiTransport(); + + it('reports errors when logging errors', async function () { + this.retries(3); + const start = Date.now(); + + const message = `an error at ${Date.now()}`; + logger.error(new Error(message)); + + const errors = await errorsTransport.pollForNewEvents( + SERVICE, + start, + ERROR_REPORTING_POLL_TIMEOUT + ); + + assert.strictEqual( + errors.length, + 1, + `expected 1 error but got ${require('util').inspect(errors)}` + ); + const errEvent = errors[0]; + + assert.strictEqual(errEvent.serviceContext.service, SERVICE); + assert(errEvent.message.includes(`Error: ${message}`)); + }); + }); +}); + +// polls for the entire array of entries to be greater than logTime. +function pollLogs( + logName: string, + logTime: number, + size: number, + timeout: number +) { + const p = new Promise((resolve, reject) => { + const end = Date.now() + timeout; + loop(); + + function loop() { + setTimeout(() => { + logging.log(logName).getEntries( + { + pageSize: size, + }, + (err, entries) => { + if (!entries || entries.length < size) return loop(); + + const {receiveTimestamp} = (entries[entries.length - 1].metadata || + {}) as {receiveTimestamp: {seconds: number; nanos: number}}; + const timeMilliseconds = + receiveTimestamp.seconds * 1000 + receiveTimestamp.nanos * 1e-6; + + if (timeMilliseconds >= logTime) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return resolve(entries as any); + } + + if (Date.now() > end) { + return reject(new Error('timeout')); + } + loop(); + } + ); + }, 500); + } + }); + + return p; +} diff --git a/handwritten/nodejs-logging-bunyan/system-test/test-install.ts b/handwritten/nodejs-logging-bunyan/system-test/test-install.ts new file mode 100644 index 00000000000..e4e0844f304 --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/system-test/test-install.ts @@ -0,0 +1,203 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as check from 'post-install-check'; + +const TS_CODE_ARRAY: check.CodeSample[] = [ + { + code: `import * as lb from '@google-cloud/logging-bunyan'; +import * as bunyan from 'bunyan'; + +const loggingBunyan = new lb.LoggingBunyan(); + +bunyan.createLogger({ + name: 'my-service', + level: 'info', + streams: [ + {stream: process.stdout}, + loggingBunyan.stream('info'), + ], +});`, + description: 'imports the module using * syntax', + dependencies: ['bunyan'], + devDependencies: ['@types/bunyan'], + }, + { + code: `import {LoggingBunyan} from '@google-cloud/logging-bunyan'; +import * as bunyan from 'bunyan'; + +const loggingBunyan = new LoggingBunyan(); + +bunyan.createLogger({ + name: 'my-service', + level: 'info', + streams: [ + {stream: process.stdout}, + loggingBunyan.stream('info'), + ], +});`, + description: 'imports the module with {} syntax', + dependencies: ['bunyan'], + devDependencies: ['@types/bunyan'], + }, + { + code: `import {LoggingBunyan} from '@google-cloud/logging-bunyan'; +import * as bunyan from 'bunyan'; + +const loggingBunyan = new LoggingBunyan({ + serviceContext: { + service: 'some service' + } +}); + +bunyan.createLogger({ + name: 'my-service', + level: 'info', + streams: [ + {stream: process.stdout}, + loggingBunyan.stream('info'), + ], +});`, + description: + 'imports the module and starts with a partial `serviceContext`', + dependencies: ['bunyan'], + devDependencies: ['@types/bunyan'], + }, + { + code: `import {LoggingBunyan} from '@google-cloud/logging-bunyan'; +import * as bunyan from 'bunyan'; + +const loggingBunyan = new LoggingBunyan({ + projectId: 'some-project', + serviceContext: { + service: 'Some service', + version: 'Some version' + } +}); + +bunyan.createLogger({ + name: 'my-service', + level: 'info', + streams: [ + {stream: process.stdout}, + loggingBunyan.stream('info'), + ], +});`, + description: + 'imports the module and starts with a complete `serviceContext`', + dependencies: ['bunyan'], + devDependencies: ['@types/bunyan'], + }, + { + code: `import * as lb from '@google-cloud/logging-bunyan'; +import * as express from 'express'; + +async function main() { + const {logger, mw} = await lb.express.middleware(); + const app = express(); + app.use(mw); +}`, + description: 'can be used with express', + dependencies: ['express', 'bunyan'], + devDependencies: ['@types/bunyan', '@types/express'], + }, +]; + +const JS_CODE_ARRAY: check.CodeSample[] = [ + { + code: `const LoggingBunyan = require('@google-cloud/logging-bunyan').LoggingBunyan; +const bunyan = require('bunyan'); + +const loggingBunyan = new LoggingBunyan(); + +bunyan.createLogger({ + name: 'my-service', + level: 'info', + streams: [ + {stream: process.stdout}, + loggingBunyan.stream('info'), + ], +});`, + description: 'requires the module using Node 4+ syntax', + dependencies: ['bunyan'], + devDependencies: [], + }, + { + code: `const LoggingBunyan = require('@google-cloud/logging-bunyan').LoggingBunyan; +const bunyan = require('bunyan'); + +const loggingBunyan = new LoggingBunyan({ + serviceContext: { + service: 'some service' + } +}); + +bunyan.createLogger({ + name: 'my-service', + level: 'info', + streams: [ + {stream: process.stdout}, + loggingBunyan.stream('info'), + ], +});`, + description: + 'requires the module and starts with a partial `serviceContext`', + dependencies: ['bunyan'], + devDependencies: [], + }, + { + code: `const LoggingBunyan = require('@google-cloud/logging-bunyan').LoggingBunyan; +const bunyan = require('bunyan'); + +const loggingBunyan = new LoggingBunyan({ + projectId: 'some-project', + serviceContext: { + service: 'Some service', + version: 'Some version' + } +}); + +bunyan.createLogger({ + name: 'my-service', + level: 'info', + streams: [ + {stream: process.stdout}, + loggingBunyan.stream('info'), + ], +});`, + description: + 'requires the module and starts with a complete `serviceContext`', + dependencies: ['bunyan'], + devDependencies: [], + }, + { + code: `const lb = require('@google-cloud/logging-bunyan'); +const express = require('express'); + +async function main() { + lb.express.middleware().then(result => { + const app = express(); + app.use(result.mw); + }).catch((err) => { + console.error(err); + process.exit(1); + }); +}`, + description: 'can be used with express', + dependencies: ['express', 'bunyan'], + devDependencies: [], + }, +]; + +check.testInstallation(TS_CODE_ARRAY, JS_CODE_ARRAY, {timeout: 5 * 60 * 1000}); diff --git a/handwritten/nodejs-logging-bunyan/system-test/test-middleware-express.ts b/handwritten/nodejs-logging-bunyan/system-test/test-middleware-express.ts new file mode 100644 index 00000000000..4e76584563e --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/system-test/test-middleware-express.ts @@ -0,0 +1,86 @@ +/*! + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* AN EXAMPLE RATHER THAN A TEST AT THIS POINT */ + +import * as assert from 'assert'; +import {describe, it, before} from 'mocha'; +import delay from 'delay'; +import * as uuid from 'uuid'; +import {express as elb} from '../src/index'; +import {Logging} from '@google-cloud/logging'; + +const logging = new Logging(); + +const WRITE_CONSISTENCY_DELAY_MS = 20 * 1000; +const TEST_TIMEOUT = WRITE_CONSISTENCY_DELAY_MS + 10 * 1000; +const LOG_NAME = `bunyan-system-test-${uuid.v4()}`; + +describe('express middleware', () => { + let logger: elb.MiddlewareReturnType['logger']; + let mw: elb.MiddlewareReturnType['mw']; + + before(async () => { + ({logger, mw} = await elb.middleware({logName: LOG_NAME, level: 'info'})); + }); + + describe('global logger', () => { + it('should properly write log entries', async function () { + this.timeout(TEST_TIMEOUT); + const LOG_MESSAGE = `unique log message ${uuid.v4()}`; + logger.info(LOG_MESSAGE); + + await delay(WRITE_CONSISTENCY_DELAY_MS); + + const log = logging.log(`${LOG_NAME}_applog`); + const entries = (await log.getEntries({pageSize: 1}))[0]; + assert.strictEqual(entries.length, 1); + assert.strictEqual(LOG_MESSAGE, entries[0].data.message); + }); + }); + + describe('request logging middleware', () => { + it('should write request correlated log entries', function (done) { + this.timeout(TEST_TIMEOUT); + const LOG_MESSAGE = `correlated log message ${uuid.v4()}`; + const fakeRequest = {headers: {fake: 'header'}}; + const fakeResponse = {}; + const next = async () => { + // At this point fakeRequest.log should have been installed. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (fakeRequest as any).log.info(LOG_MESSAGE); + + await delay(WRITE_CONSISTENCY_DELAY_MS); + + const log = logging.log(`${LOG_NAME}_applog`); + const entries = (await log.getEntries({pageSize: 1}))[0]; + assert.strictEqual(entries.length, 1); + const entry = entries[0]; + assert.strictEqual(LOG_MESSAGE, entry.data.message); + assert(entry.metadata.trace, 'should have a trace property'); + assert(entry.metadata.trace!.match(/projects\/.*\/traces\/.*/)); + assert(entry.metadata.spanId, 'should have a span property'); + assert(entry.metadata.spanId!.match(/^[0-9]*/)); + assert.strictEqual(entry.metadata.traceSampled, false); + done(); + }; + + // Call middleware with mocks. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + mw(fakeRequest as any, fakeResponse as any, next); + }); + }); +}); diff --git a/handwritten/nodejs-logging-bunyan/test/index.ts b/handwritten/nodejs-logging-bunyan/test/index.ts new file mode 100644 index 00000000000..6ebd30b08d0 --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/test/index.ts @@ -0,0 +1,744 @@ +// Copyright 2017 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +import * as assert from 'assert'; +import {describe, it, beforeEach, afterEach} from 'mocha'; +import * as proxyquire from 'proxyquire'; +import {inspect} from 'util'; + +import {LoggingBunyan} from '../src'; +import * as types from '../src/types/core'; + +interface Options { + logName?: string; + resource: {}; + serviceContext: { + service: string; + }; + apiEndpoint: string; + jsonFieldsToTruncate: string[]; +} +interface FakeLogType { + entry?: () => void; + write?: () => void; + logging: {auth: {getEnv: Function}}; +} + +describe('logging-bunyan', () => { + const FAKE_LOG_INSTANCE: FakeLogType = { + logging: { + auth: { + getEnv: () => { + return 'foo'; + }, + }, + }, + }; + let fakeLogInstance: FakeLogType; + let fakeLoggingOptions_: types.Options | null; + let fakeLogName_: string | null; + let fakeLogOptions_: types.Options; + let fakeWritableOptions_: types.Options; + let fakeDetectedServiceContext: types.ServiceContext | null; + + function fakeLogging(options: types.Options) { + fakeLoggingOptions_ = options; + return { + log(logName: string, options: types.Options) { + fakeLogName_ = logName; + fakeLogOptions_ = options; + return fakeLogInstance; + }, + }; + } + + function FakeWritable(options: types.Options) { + fakeWritableOptions_ = options; + } + + // Writable.write used 'any' in function signature. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + FakeWritable.prototype.write = ( + chunk: {}, + encoding: string, + callback: Function + ) => { + // Function cannot pass as type in setImmediate. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + setImmediate(callback as any); + }; + + const fakeStream = { + Writable: FakeWritable, + }; + + const fakeDetectServiceContext = () => { + if (fakeDetectedServiceContext === null) { + return Promise.reject(new Error('fake error')); + } + return Promise.resolve(fakeDetectedServiceContext); + }; + const loggingBunyanLib = proxyquire('../src/index.js', { + '@google-cloud/logging': { + Logging: fakeLogging, + detectServiceContext: fakeDetectServiceContext, + }, + stream: fakeStream, + }); + const loggingBunyanCached = proxyquire('../src/index.js', { + '@google-cloud/logging': { + Logging: fakeLogging, + detectServiceContext: fakeDetectServiceContext, + }, + stream: fakeStream, + }); + + // loggingBunyan is loggingBunyan namespace which cannot be determined type. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let loggingBunyan: any; + + const TRUNCATE_FIELD = + 'jsonPayload.fields.metadata.structValue.fields.custom.stringValue'; + + const OPTIONS = { + logName: 'log-name', + resource: {}, + serviceContext: { + service: 'fake-service', + }, + apiEndpoint: 'fake.local', + jsonFieldsToTruncate: [TRUNCATE_FIELD], + }; + + const RECORD = { + level: 30, + time: '2012-06-19T21:34:19.906Z', + }; + + beforeEach(() => { + fakeLogInstance = {...FAKE_LOG_INSTANCE}; + fakeLoggingOptions_ = null; + fakeLogName_ = null; + fakeDetectedServiceContext = null; + + Object.assign(true, loggingBunyanLib.LoggingBunyan, loggingBunyanCached); + loggingBunyan = new loggingBunyanLib.LoggingBunyan(OPTIONS); + }); + + describe('instantiation', () => { + it('should be an object mode Writable', () => { + assert(loggingBunyan instanceof FakeWritable); + assert.deepStrictEqual(fakeWritableOptions_, {objectMode: true}); + }); + + it('should localize the provided resource', () => { + assert.strictEqual(loggingBunyan.resource, OPTIONS.resource); + }); + + it('should localize the provided service context', () => { + assert.strictEqual(loggingBunyan.serviceContext, OPTIONS.serviceContext); + }); + + it('should localize Log instance using provided name', () => { + assert.strictEqual(fakeLoggingOptions_, OPTIONS); + assert.strictEqual(fakeLogName_, OPTIONS.logName); + }); + + it('should localize Log instance using provided jsonFieldsToTruncate in options', () => { + assert.strictEqual(fakeLoggingOptions_, OPTIONS); + assert.strictEqual( + fakeLogOptions_.jsonFieldsToTruncate, + OPTIONS.jsonFieldsToTruncate + ); + }); + + it('should localize Log instance using default name, removeCircular and maxEntrySize options', () => { + const optionsWithoutLogName: Options = Object.assign({}, OPTIONS); + delete optionsWithoutLogName.logName; + new loggingBunyanLib.LoggingBunyan(optionsWithoutLogName); + assert.strictEqual(fakeLoggingOptions_, optionsWithoutLogName); + assert.strictEqual(fakeLogName_, 'bunyan_log'); + assert.deepStrictEqual(fakeLogOptions_, { + removeCircular: true, + maxEntrySize: 250000, + jsonFieldsToTruncate: [TRUNCATE_FIELD], + }); + }); + + it('should not throw if a serviceContext is not specified', () => { + // tslint:disable-next-line:no-unused-expression + new loggingBunyanLib.LoggingBunyan(); + }); + + it('should throw if a serviceContext is specified without a service', done => { + try { + // tslint:disable-next-line:no-unused-expression + new loggingBunyanLib.LoggingBunyan({serviceContext: {}}); + } catch (err) { + assert.strictEqual( + (err as Error).message, + "If 'serviceContext' is specified then " + + "'serviceContext.service' is required." + ); + done(); + } + }); + + it('should not attempt to discover service context if passed', () => { + const serviceContext = {service: 'foo'}; + // tslint:disable-next-line:no-unused-expression + new loggingBunyanLib.LoggingBunyan({serviceContext}); + }); + + it('should attempt to discover service context if not passed', done => { + const serviceContext = {service: 'foo'}; + fakeDetectedServiceContext = serviceContext; + const lb = new loggingBunyanLib.LoggingBunyan(); + assert.strictEqual(lb.serviceContext, undefined); + setTimeout(() => { + assert.deepStrictEqual(lb.serviceContext, serviceContext); + done(); + }, 10); + }); + + it('should handle errors in discovering service context', done => { + fakeDetectedServiceContext = null; + const lb = new loggingBunyanLib.LoggingBunyan(); + assert.strictEqual(lb.serviceContext, undefined); + setTimeout(() => { + assert.deepStrictEqual(lb.serviceContext, undefined); + done(); + }, 10); + }); + }); + + describe('stream', () => { + it('should return a properly formatted object', () => { + const level = 'info'; + const stream = loggingBunyan.stream(level); + + assert.strictEqual(stream.level, level); + assert.strictEqual(stream.type, 'raw'); + assert.strictEqual(stream.stream, loggingBunyan); + }); + }); + + describe('properLabels', () => { + it('should validate labels correctly', () => { + const properLabels = [ + {}, + [], + {key: 'value'}, + ['a', 'b'], + {a: 'b', c: 'd'}, + { + key: 'value', + [Symbol('symbolKey')]: 'value2', + }, // symbol gets ignored. + ]; + const improperLabels = [ + true, + false, + undefined, + -1, + NaN, + () => {}, + 'a string', + Symbol('a symbol'), + {key: {nested: 'object'}}, + {key: -1}, + {key: false}, + {key: Symbol('another symbol')}, + ]; + + for (const labels of properLabels) { + assert.strictEqual( + true, + LoggingBunyan.properLabels(labels), + `expected ${inspect(labels)} to be proper` + ); + } + for (const labels of improperLabels) { + assert.strictEqual( + false, + LoggingBunyan.properLabels(labels), + `expected ${inspect(labels)} to be improper` + ); + } + }); + }); + + describe('formatEntry_', () => { + it('should throw an error if record is a string', () => { + assert.throws(() => { + loggingBunyan.formatEntry_('string record'); + }, new RegExp('@google-cloud/logging-bunyan only works as a raw bunyan stream type.')); + }); + + it('should properly create an entry', done => { + loggingBunyan.cloudLog.entry = ( + entryMetadata: types.StackdriverEntryMetadata, + record: types.StackdriverEntryMetadata + ) => { + assert.deepStrictEqual(entryMetadata, { + resource: loggingBunyan.resource, + timestamp: RECORD.time, + severity: 'INFO', + }); + assert.deepStrictEqual(record, RECORD); + done(); + }; + + loggingBunyan.formatEntry_(RECORD); + }); + + it('should rename the msg property to message', done => { + const recordWithMsg = Object.assign({msg: 'msg'}, RECORD); + const recordWithMessage = Object.assign({message: 'msg'}, RECORD); + + loggingBunyan.cloudLog.entry = ( + entryMetadata: types.StackdriverEntryMetadata, + record: types.StackdriverEntryMetadata + ) => { + assert.deepStrictEqual(record, recordWithMessage); + done(); + }; + + loggingBunyan.formatEntry_(recordWithMsg); + }); + + it('should inject the error stack as the message', done => { + const record = Object.assign( + { + msg: 'msg', + err: { + stack: 'the stack', + }, + }, + RECORD + ); + const expectedRecord = Object.assign( + { + msg: 'msg', + err: { + stack: 'the stack', + }, + message: 'the stack', + serviceContext: OPTIONS.serviceContext, + }, + RECORD + ); + + loggingBunyan.cloudLog.entry = ( + entryMetadata: types.StackdriverEntryMetadata, + record_: types.StackdriverEntryMetadata + ) => { + assert.deepStrictEqual(record_, expectedRecord); + done(); + }; + + loggingBunyan.formatEntry_(record); + }); + + it('should leave message property intact when present', done => { + const record = Object.assign( + { + msg: 'msg', + message: 'message', + err: { + stack: 'the stack', + }, + }, + RECORD + ); + + loggingBunyan.cloudLog.entry = ( + entryMetadata: types.StackdriverEntryMetadata, + record_: types.StackdriverEntryMetadata + ) => { + assert.deepStrictEqual(record_, record); + done(); + }; + + loggingBunyan.formatEntry_(record); + }); + + it('should promote the httpRequest property to metadata', done => { + const HTTP_REQUEST = { + statusCode: 418, + }; + const recordWithRequest = Object.assign( + { + httpRequest: HTTP_REQUEST, + }, + RECORD + ); + + loggingBunyan.cloudLog.entry = ( + entryMetadata: types.StackdriverEntryMetadata, + record: string | types.BunyanLogRecord + ) => { + assert.deepStrictEqual(entryMetadata, { + resource: loggingBunyan.resource, + timestamp: RECORD.time, + severity: 'INFO', + httpRequest: HTTP_REQUEST, + }); + assert.deepStrictEqual(record, RECORD); + done(); + }; + + loggingBunyan.formatEntry_(recordWithRequest); + }); + + it('should promote properly formatted labels to metadata', done => { + const labels = {key: 'value', 0: 'value2'}; + const recordWithLabels = {...RECORD, labels}; + loggingBunyan.cloudLog.entry = ( + entryMetadata: types.StackdriverEntryMetadata, + record: string | types.BunyanLogRecord + ) => { + assert.deepStrictEqual(entryMetadata.labels, labels); + assert.deepStrictEqual(record, RECORD); + done(); + }; + loggingBunyan.formatEntry_(recordWithLabels); + }); + + it('should not promote ill-formatted labels to metadata', done => { + const labels = {key: -1}; // values must be strings. + const recordWithLabels = {...RECORD, labels}; + loggingBunyan.cloudLog.entry = ( + entryMetadata: types.StackdriverEntryMetadata, + record: string | types.BunyanLogRecord + ) => { + assert(entryMetadata.labels === undefined); + assert.deepStrictEqual(record, recordWithLabels); + done(); + }; + loggingBunyan.formatEntry_(recordWithLabels); + }); + + it('should promote prefixed trace properties to metadata', done => { + const recordWithTrace = Object.assign({}, RECORD); + // recordWithTrace does not have index signature. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (recordWithTrace as any)[loggingBunyanLib.LOGGING_TRACE_KEY] = 'trace1'; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (recordWithTrace as any)[loggingBunyanLib.LOGGING_SPAN_KEY] = 'span1'; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (recordWithTrace as any)[loggingBunyanLib.LOGGING_SAMPLED_KEY] = true; + + loggingBunyan.cloudLog.entry = ( + entryMetadata: types.StackdriverEntryMetadata, + record: string | types.BunyanLogRecord + ) => { + assert.deepStrictEqual(entryMetadata, { + resource: loggingBunyan.resource, + timestamp: RECORD.time, + severity: 'INFO', + trace: 'trace1', + spanId: 'span1', + traceSampled: true, + }); + assert.deepStrictEqual(record, RECORD); + done(); + }; + + loggingBunyan.formatEntry_(recordWithTrace); + }); + + it('should promote a `false` traceSampled property to metadata', done => { + const recordWithTrace = Object.assign({}, RECORD); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (recordWithTrace as any)[loggingBunyanLib.LOGGING_SAMPLED_KEY] = false; + + loggingBunyan.cloudLog.entry = ( + entryMetadata: types.StackdriverEntryMetadata, + record: string | types.BunyanLogRecord + ) => { + assert.deepStrictEqual(entryMetadata, { + resource: loggingBunyan.resource, + timestamp: RECORD.time, + severity: 'INFO', + traceSampled: false, + }); + assert.deepStrictEqual(record, RECORD); + done(); + }; + + loggingBunyan.formatEntry_(recordWithTrace); + }); + }); + + describe('write', () => { + const oldWritableWrite = FakeWritable.prototype.write; + const oldTraceAgent = global._google_trace_agent; + + afterEach(() => { + FakeWritable.prototype.write = oldWritableWrite; + global._google_trace_agent = oldTraceAgent; + }); + + it('should not set trace property if trace unavailable', done => { + global._google_trace_agent = undefined; + + FakeWritable.prototype.write = function ( + // Writable.write used 'any' in function signature. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + record: any, + encoding: string, + callback: Function + ) { + assert.deepStrictEqual(record, RECORD); + assert.strictEqual(encoding, 'encoding'); + assert.strictEqual(callback, assert.ifError); + assert.strictEqual(this, loggingBunyan); + done(); + }; + + loggingBunyan.write(RECORD, 'encoding', assert.ifError); + }); + + it('should set prefixed trace property if trace available', done => { + global._google_trace_agent = { + getCurrentContextId: () => { + return 'trace1'; + }, + getWriterProjectId: () => { + return 'project1'; + }, + }; + const recordWithoutTrace = Object.assign({}, RECORD); + const recordWithTrace = Object.assign({}, RECORD); + // recordWithTrace does not have index signature. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (recordWithTrace as any)[loggingBunyanLib.LOGGING_TRACE_KEY] = + 'projects/project1/traces/trace1'; + + FakeWritable.prototype.write = function ( + // Writable.write used 'any' in function signature. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + record: any, + encoding: string, + callback: Function + ) { + // Check that trace field added to record before calling Writable.write + assert.deepStrictEqual(record, recordWithTrace); + + // Check that the original record passed in was not mutated + assert.deepStrictEqual(recordWithoutTrace, RECORD); + + assert.strictEqual(encoding, 'encoding'); + assert.strictEqual(callback, assert.ifError); + assert.strictEqual(this, loggingBunyan); + done(); + }; + + loggingBunyan.write(recordWithoutTrace, 'encoding', assert.ifError); + }); + + it('should leave prefixed trace property as is if set', done => { + const oldTraceAgent = global._google_trace_agent; + global._google_trace_agent = { + getCurrentContextId: () => { + return 'trace-from-agent'; + }, + getWriterProjectId: () => { + return 'project1'; + }, + }; + const recordWithTraceAlreadySet = Object.assign({}, RECORD); + // recordWithTraceAlreadySet does not have index signature. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (recordWithTraceAlreadySet as any)[loggingBunyanLib.LOGGING_TRACE_KEY] = + 'trace1'; + + FakeWritable.prototype.write = function ( + // Writable.write used 'any' in function signature. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + record: any, + encoding: string, + callback: Function + ) { + assert.deepStrictEqual(record, recordWithTraceAlreadySet); + assert.strictEqual(encoding, ''); + assert.strictEqual(callback, assert.ifError); + assert.strictEqual(this, loggingBunyan); + done(); + }; + + loggingBunyan.write(recordWithTraceAlreadySet, '', assert.ifError); + + global._google_trace_agent = oldTraceAgent; + }); + }); + + it('should not set prefixed trace property if trace unavailable', () => { + FakeWritable.prototype.write = function ( + // Writable.write used 'any' in function signature. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + record: any, + encoding: string, + callback: Function + ) { + assert.deepStrictEqual(record, RECORD); + assert.strictEqual(encoding, ''); + assert.strictEqual(callback, assert.ifError); + assert.strictEqual(this, loggingBunyan); + }; + const oldTraceAgent = global._google_trace_agent; + + global._google_trace_agent = {}; + loggingBunyan.write(RECORD, '', assert.ifError); + + global._google_trace_agent = { + getCurrentContextId: () => { + return null; + }, + getWriterProjectId: () => { + return null; + }, + }; + loggingBunyan.write(RECORD, '', assert.ifError); + global._google_trace_agent = { + getCurrentContextId: () => { + return null; + }, + getWriterProjectId: () => { + return 'project1'; + }, + }; + loggingBunyan.write(RECORD, '', assert.ifError); + + global._google_trace_agent = { + getCurrentContextId: () => { + return 'trace1'; + }, + getWriterProjectId: () => { + return null; + }, + }; + loggingBunyan.write(RECORD, '', assert.ifError); + + global._google_trace_agent = oldTraceAgent; + }); + + describe('_write', () => { + beforeEach(() => { + fakeLogInstance.entry = () => {}; + fakeLogInstance.write = () => {}; + }); + + it('should format the record', done => { + loggingBunyan.formatEntry_ = (record: string | types.BunyanLogRecord) => { + assert.strictEqual(record, RECORD); + done(); + }; + + loggingBunyan._write(RECORD, '', assert.ifError); + }); + + it('should write the record to the log instance', done => { + const entry = {}; + + loggingBunyan.cloudLog.entry = () => { + return entry; + }; + + loggingBunyan.cloudLog.write = + // Writable.write used 'any' in function signature. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (entries: any, callback: Function) => { + assert.strictEqual(entries, entry); + callback(); // done() + }; + + loggingBunyan._write(RECORD, '', done); + }); + + it('should write the record and call default callback', done => { + let isCallbackCalled = false; + loggingBunyan.cloudLog.entry = () => { + return {}; + }; + loggingBunyan.defaultCallback = () => { + isCallbackCalled = true; + }; + loggingBunyan.cloudLog.write = + // Writable.write used 'any' in function signature. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (entries: any, callback: Function) => { + callback(); + }; + loggingBunyan._write(RECORD, '', () => {}); + assert.strictEqual(isCallbackCalled, true); + done(); + }); + }); + + describe('_writev', () => { + const RECORDS = [{chunk: RECORD}, {chunk: RECORD}]; + beforeEach(() => { + fakeLogInstance.entry = () => {}; + fakeLogInstance.write = () => {}; + }); + + it('should format the records', done => { + let numFormatted = 0; + loggingBunyan.formatEntry_ = (record: string | types.BunyanLogRecord) => { + assert.strictEqual(record, RECORD); + if (++numFormatted === RECORDS.length) { + done(); + } + }; + + loggingBunyan._writev(RECORDS, assert.ifError); + }); + + it('should write the records to the log instance', done => { + const entry = {}; + + loggingBunyan.cloudLog.entry = () => { + return entry; + }; + + loggingBunyan.cloudLog.write = + // Writable.write used 'any' in function signature. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (entries: any, callback: Function) => { + assert.deepStrictEqual(entries, [entry, entry]); + callback(); // done() + }; + + loggingBunyan._writev(RECORDS, done); + }); + }); + + describe('BUNYAN_TO_STACKDRIVER', () => { + it('should correctly map to Stackdriver Logging levels', () => { + const bunyanToStackdriver: Map = new Map([ + [60, 'CRITICAL'], + [50, 'ERROR'], + [40, 'WARNING'], + [30, 'INFO'], + [20, 'DEBUG'], + [10, 'DEBUG'], + ]); + assert.deepStrictEqual( + loggingBunyanLib.BUNYAN_TO_STACKDRIVER, + bunyanToStackdriver + ); + }); + }); +}); diff --git a/handwritten/nodejs-logging-bunyan/test/middleware/test-express.ts b/handwritten/nodejs-logging-bunyan/test/middleware/test-express.ts new file mode 100644 index 00000000000..1469f0deabd --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/test/middleware/test-express.ts @@ -0,0 +1,143 @@ +/*! + * Copyright 2018 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as assert from 'assert'; +import {describe, it, beforeEach} from 'mocha'; +import {GCPEnv} from 'google-auth-library'; +import * as proxyquire from 'proxyquire'; + +// types-only import. Actual require is done through proxyquire below. +import {MiddlewareOptions} from '../../src/middleware/express'; + +const FAKE_PROJECT_ID = 'project-🦄'; +const FAKE_GENERATED_MIDDLEWARE = () => {}; + +const FAKE_ENVIRONMENT = 'FAKE_ENVIRONMENT'; + +let authEnvironment: string; +let passedOptions: Array; + +class FakeLoggingBunyan { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + cloudLog: any; + constructor(options: MiddlewareOptions) { + passedOptions.push(options); + this.cloudLog = { + logging: { + auth: { + async getProjectId() { + return FAKE_PROJECT_ID; + }, + async getEnv() { + return authEnvironment; + }, + }, + }, + }; + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + stream(level: any) { + return {level, type: 'raw', stream: this}; + } +} + +let passedProjectId: string | undefined; +let passedEmitRequestLog: Function | undefined; +function fakeMakeMiddleware( + projectId: string, + makeChildLogger: Function, + emitRequestLog: Function +): Function { + passedProjectId = projectId; + passedEmitRequestLog = emitRequestLog; + return FAKE_GENERATED_MIDDLEWARE; +} + +const {middleware, APP_LOG_SUFFIX} = proxyquire( + '../../src/middleware/express', + { + '../../src/index': {LoggingBunyan: FakeLoggingBunyan}, + '@google-cloud/logging': { + middleware: {express: {makeMiddleware: fakeMakeMiddleware}}, + }, + } +); + +describe('middleware/express', () => { + beforeEach(() => { + passedOptions = []; + passedProjectId = undefined; + passedEmitRequestLog = undefined; + authEnvironment = FAKE_ENVIRONMENT; + }); + + it('should create and return a middleware', async () => { + const {mw} = await middleware(); + assert.strictEqual(mw, FAKE_GENERATED_MIDDLEWARE); + }); + + it('should generate two loggers with default logName and level', async () => { + await middleware(); + // Should generate two loggers with the expected names. + assert.ok(passedOptions); + assert.strictEqual(passedOptions.length, 2); + assert.ok( + passedOptions.some( + option => option!.logName === `bunyan_log_${APP_LOG_SUFFIX}` + ) + ); + assert.ok(passedOptions.some(option => option!.logName === 'bunyan_log')); + assert.ok(passedOptions.every(option => option!.level === 'info')); + }); + + it('should prefer user-provided logName and level', async () => { + const LOGNAME = '㏒'; + const LEVEL = 'fatal'; + const OPTIONS = {logName: LOGNAME, level: LEVEL}; + await middleware(OPTIONS); + assert.ok(passedOptions); + assert.strictEqual(passedOptions.length, 2); + assert.ok( + passedOptions.some( + option => option!.logName === `${LOGNAME}_${APP_LOG_SUFFIX}` + ) + ); + assert.ok(passedOptions.some(option => option!.logName === LOGNAME)); + assert.ok(passedOptions.every(option => option!.level === LEVEL)); + }); + + it('should acquire the projectId and pass to makeMiddleware', async () => { + await middleware(); + assert.strictEqual(passedProjectId, FAKE_PROJECT_ID); + }); + + [GCPEnv.APP_ENGINE, GCPEnv.CLOUD_FUNCTIONS, GCPEnv.CLOUD_RUN].forEach(env => { + it(`should not generate the request logger on ${env}`, async () => { + authEnvironment = env; + if (env === GCPEnv.CLOUD_RUN) { + // Cloud Run needs explicit option flag to enable this behavior until we can make breaking change in next major version + await middleware({skipParentEntryForCloudRun: true}); + } else { + await middleware(); + } + assert.ok(passedOptions); + assert.strictEqual(passedOptions.length, 1); + // emitRequestLog parameter to makeChildLogger should be undefined. + assert.strictEqual(passedEmitRequestLog, undefined); + }); + }); +}); diff --git a/handwritten/nodejs-logging-bunyan/tsconfig.json b/handwritten/nodejs-logging-bunyan/tsconfig.json new file mode 100644 index 00000000000..f32d1c35a7a --- /dev/null +++ b/handwritten/nodejs-logging-bunyan/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "./node_modules/gts/tsconfig-google.json", + "compilerOptions": { + "rootDir": ".", + "outDir": "build", + "lib": ["es2018", "dom"] + }, + "include": [ + "src/*.ts", + "src/**/*.ts", + "test/*.ts", + "test/**/*.ts", + "system-test/*.ts", + "system-test/**/*.ts" + ] +} diff --git a/release-please-config.json b/release-please-config.json index 9c211610789..5dc5c58f93f 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -1,6 +1,8 @@ { + "bump-minor-pre-major": true, "initial-version": "0.1.0", "packages": { + "handwritten/nodejs-logging-bunyan": {}, "packages/gapic-node-processing": {}, "packages/google-ads-admanager": {}, "packages/google-ads-datamanager": {}, @@ -149,11 +151,11 @@ "packages/google-cloud-saasplatform-saasservicemgmt": {}, "packages/google-cloud-scheduler": {}, "packages/google-cloud-secretmanager": {}, + "packages/google-cloud-securesourcemanager": {}, "packages/google-cloud-security-privateca": {}, "packages/google-cloud-security-publicca": {}, "packages/google-cloud-securitycenter": {}, "packages/google-cloud-securitycentermanagement": {}, - "packages/google-cloud-securesourcemanager": {}, "packages/google-cloud-servicedirectory": {}, "packages/google-cloud-servicehealth": {}, "packages/google-cloud-shell": {}, @@ -227,6 +229,5 @@ "type": "sentence-case" } ], - "bump-minor-pre-major": true, "release-type": "node" } \ No newline at end of file