├── .eslintignore ├── .eslintrc.json ├── .gitattributes ├── .github ├── .OwlBot.lock.yaml ├── .OwlBot.yaml ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ ├── documentation_request.yml │ ├── feature_request.yml │ ├── processs_request.md │ └── questions.md ├── PULL_REQUEST_TEMPLATE.md ├── auto-approve.yml ├── auto-label.yaml ├── blunderbuss.yml ├── flakybot.yaml ├── generated-files-bot.yml ├── release-please.yml ├── release-trigger.yml ├── scripts │ ├── close-invalid-link.cjs │ ├── close-unresponsive.cjs │ └── remove-response-label.cjs ├── sync-repo-settings.yaml └── workflows │ ├── ci.yaml │ ├── issues-no-repro.yaml │ └── response.yaml ├── .gitignore ├── .jsdoc.js ├── .kokoro ├── .gitattributes ├── common.cfg ├── common_env_vars.cfg ├── continuous │ └── node14 │ │ ├── common.cfg │ │ ├── lint.cfg │ │ ├── samples-test.cfg │ │ ├── system-test.cfg │ │ └── test.cfg ├── docs.sh ├── lint.sh ├── populate-secrets.sh ├── presubmit │ ├── node14 │ │ ├── common.cfg │ │ ├── samples-test.cfg │ │ ├── system-test.cfg │ │ └── test.cfg │ └── windows │ │ ├── common.cfg │ │ └── test.cfg ├── publish.sh ├── release │ ├── common.cfg │ ├── docs-devsite.cfg │ ├── docs-devsite.sh │ ├── docs.cfg │ ├── docs.sh │ └── publish.cfg ├── samples-test.sh ├── system-test.sh ├── test.bat ├── test.sh ├── trampoline.sh └── trampoline_v2.sh ├── .mocharc.js ├── .nycrc ├── .prettierignore ├── .prettierrc.js ├── .readme-partials.yml ├── .repo-metadata.json ├── .trampolinerc ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── doc └── images │ ├── bunyan-logs-in-trace.png │ ├── request-bundling.png │ └── request-log.png ├── linkinator.config.json ├── owlbot.py ├── package.json ├── renovate.json ├── samples ├── .eslintrc.yml ├── README.md ├── express.js ├── package.json ├── quickstart.js ├── setup_explicit.js └── system-test │ ├── express.test.js │ └── quickstart.test.js ├── src ├── index.ts ├── middleware │ └── express.ts └── types │ ├── core.d.ts │ └── global.d.ts ├── system-test ├── errors-transport.ts ├── logging-bunyan.ts ├── test-install.ts └── test-middleware-express.ts ├── test ├── index.ts └── middleware │ └── test-express.ts └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/coverage 3 | test/fixtures 4 | build/ 5 | docs/ 6 | protos/ 7 | samples/generated/ 8 | system-test/**/fixtures 9 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/gts" 3 | } 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.ts text eol=lf 2 | *.js text eol=lf 3 | protos/* linguist-generated 4 | **/api-extractor.json linguist-language=JSON-with-Comments 5 | -------------------------------------------------------------------------------- /.github/.OwlBot.lock.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | docker: 15 | image: gcr.io/cloud-devrel-public-resources/owlbot-nodejs:latest 16 | digest: sha256:609822e3c09b7a1bd90b99655904609f162cc15acb4704f1edf778284c36f429 17 | # created: 2024-10-01T19:34:30.797530443Z 18 | -------------------------------------------------------------------------------- /.github/.OwlBot.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | docker: 15 | image: gcr.io/cloud-devrel-public-resources/owlbot-nodejs:latest 16 | 17 | 18 | begin-after-commit-hash: 674a41e0de2869f44f45eb7b1a605852a5394bba 19 | 20 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Code owners file. 2 | # This file controls who is tagged for review for any given pull request. 3 | # 4 | # For syntax help see: 5 | # https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax 6 | 7 | 8 | # The yoshi-nodejs team is the default owner for nodejs repositories. 9 | * @googleapis/api-logging @googleapis/api-logging-partners @jsteam 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Create a report to help us improve 3 | labels: 4 | - bug 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: > 9 | **PLEASE READ**: If you have a support contract with Google, please 10 | create an issue in the [support 11 | console](https://cloud.google.com/support/) instead of filing on GitHub. 12 | This will ensure a timely response. Otherwise, please make sure to 13 | follow the steps below. 14 | - type: checkboxes 15 | attributes: 16 | label: Please make sure you have searched for information in the following 17 | guides. 18 | options: 19 | - label: "Search the issues already opened: 20 | https://github.com/GoogleCloudPlatform/google-cloud-node/issues" 21 | required: true 22 | - label: "Search StackOverflow: 23 | http://stackoverflow.com/questions/tagged/google-cloud-platform+nod\ 24 | e.js" 25 | required: true 26 | - label: "Check our Troubleshooting guide: 27 | https://github.com/googleapis/google-cloud-node/blob/main/docs/trou\ 28 | bleshooting.md" 29 | required: true 30 | - label: "Check our FAQ: 31 | https://github.com/googleapis/google-cloud-node/blob/main/docs/faq.\ 32 | md" 33 | required: true 34 | - label: "Check our libraries HOW-TO: 35 | https://github.com/googleapis/gax-nodejs/blob/main/client-libraries\ 36 | .md" 37 | required: true 38 | - label: "Check out our authentication guide: 39 | https://github.com/googleapis/google-auth-library-nodejs" 40 | required: true 41 | - label: "Check out handwritten samples for many of our APIs: 42 | https://github.com/GoogleCloudPlatform/nodejs-docs-samples" 43 | required: true 44 | - type: textarea 45 | attributes: 46 | label: > 47 | A screenshot that you have tested with "Try this API". 48 | description: > 49 | As our client libraries are mostly autogenerated, we kindly request 50 | that you test whether your issue is with the client library, or with the 51 | API itself. To do so, please search for your API 52 | here: https://developers.google.com/apis-explorer and attempt to 53 | reproduce the issue in the given method. Please include a screenshot of 54 | the response in "Try this API". This response should NOT match the current 55 | behavior you are experiencing. If the behavior is the same, it means 56 | that you are likely experiencing a bug with the API itself. In that 57 | case, please submit an issue to the API team, either by submitting an 58 | issue in its issue tracker (https://cloud.google.com/support/docs/issue-trackers), or by 59 | submitting an issue in its linked tracker in the .repo-metadata.json 60 | file https://issuetracker.google.com/savedsearches/559764 61 | validations: 62 | required: true 63 | - type: input 64 | attributes: 65 | label: > 66 | Link to the code that reproduces this issue. A link to a **public** Github Repository or gist with a minimal 67 | reproduction. 68 | description: > 69 | **Skipping this or providing an invalid link will result in the issue being closed** 70 | validations: 71 | required: true 72 | - type: textarea 73 | attributes: 74 | label: > 75 | A step-by-step description of how to reproduce the issue, based on 76 | the linked reproduction. 77 | description: > 78 | Screenshots can be provided in the issue body below. 79 | placeholder: | 80 | 1. Start the application in development (next dev) 81 | 2. Click X 82 | 3. Y will happen 83 | validations: 84 | required: true 85 | - type: textarea 86 | attributes: 87 | label: A clear and concise description of what the bug is, and what you 88 | expected to happen. 89 | placeholder: Following the steps from the previous section, I expected A to 90 | happen, but I observed B instead 91 | validations: 92 | required: true 93 | 94 | - type: textarea 95 | attributes: 96 | label: A clear and concise description WHY you expect this behavior, i.e., was it a recent change, there is documentation that points to this behavior, etc. ** 97 | placeholder: 'Documentation here(link) states that B should happen instead of A' 98 | validations: 99 | required: true 100 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: Google Cloud Support 3 | url: https://cloud.google.com/support/ 4 | about: If you have a support contract with Google, please use the Google Cloud Support portal. 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation_request.yml: -------------------------------------------------------------------------------- 1 | name: Documentation Requests 2 | description: Requests for more information 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: > 7 | Please use this issue type to log documentation requests against the library itself. 8 | These requests should involve documentation on Github (`.md` files), and should relate to the library 9 | itself. If you have questions or documentation requests for an API, please 10 | reach out to the API tracker itself. 11 | 12 | Please submit an issue to the API team, either by submitting an 13 | issue in its issue tracker https://cloud.google.com/support/docs/issue-trackers), or by 14 | submitting an issue in its linked tracker in the .repo-metadata.json 15 | file in the API under packages/* ([example](https://issuetracker.google.com/savedsearches/559764)). 16 | You can also submit a request to documentation on cloud.google.com itself with the "Send Feedback" 17 | on the bottom of the page. 18 | 19 | 20 | Please note that documentation requests and questions for specific APIs 21 | will be closed. 22 | - type: checkboxes 23 | attributes: 24 | label: Please make sure you have searched for information in the following 25 | guides. 26 | options: 27 | - label: "Search the issues already opened: 28 | https://github.com/GoogleCloudPlatform/google-cloud-node/issues" 29 | required: true 30 | - label: "Check our Troubleshooting guide: 31 | https://googlecloudplatform.github.io/google-cloud-node/#/docs/guid\ 32 | es/troubleshooting" 33 | required: true 34 | - label: "Check our FAQ: 35 | https://googlecloudplatform.github.io/google-cloud-node/#/docs/guid\ 36 | es/faq" 37 | required: true 38 | - label: "Check our libraries HOW-TO: 39 | https://github.com/googleapis/gax-nodejs/blob/main/client-libraries\ 40 | .md" 41 | required: true 42 | - label: "Check out our authentication guide: 43 | https://github.com/googleapis/google-auth-library-nodejs" 44 | required: true 45 | - label: "Check out handwritten samples for many of our APIs: 46 | https://github.com/GoogleCloudPlatform/nodejs-docs-samples" 47 | required: true 48 | - type: textarea 49 | attributes: 50 | label: > 51 | Documentation Request 52 | validations: 53 | required: true 54 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest an idea for this library 3 | labels: 4 | - feature request 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: > 9 | **PLEASE READ**: If you have a support contract with Google, please 10 | create an issue in the [support 11 | console](https://cloud.google.com/support/) instead of filing on GitHub. 12 | This will ensure a timely response. Otherwise, please make sure to 13 | follow the steps below. 14 | - type: textarea 15 | attributes: 16 | label: > 17 | A screenshot that you have tested with "Try this API". 18 | description: > 19 | As our client libraries are mostly autogenerated, we kindly request 20 | that you test whether your feature request is with the client library, or with the 21 | API itself. To do so, please search for your API 22 | here: https://developers.google.com/apis-explorer and attempt to 23 | reproduce the issue in the given method. Please include a screenshot of 24 | the response in "Try this API". This response should NOT match the current 25 | behavior you are experiencing. If the behavior is the same, it means 26 | that you are likely requesting a feature for the API itself. In that 27 | case, please submit an issue to the API team, either by submitting an 28 | issue in its issue tracker https://cloud.google.com/support/docs/issue-trackers, or by 29 | submitting an issue in its linked tracker in the .repo-metadata.json 30 | file in the API under packages/* ([example](https://issuetracker.google.com/savedsearches/559764)) 31 | 32 | Example of library specific issues would be: retry strategies, authentication questions, or issues with typings. 33 | Examples of API issues would include: expanding method parameter types, adding functionality to an API. 34 | validations: 35 | required: true 36 | - type: textarea 37 | attributes: 38 | label: > 39 | What would you like to see in the library? 40 | description: > 41 | Screenshots can be provided in the issue body below. 42 | placeholder: | 43 | 1. Set up authentication like so 44 | 2. Run the program like so 45 | 3. X would be nice to happen 46 | 47 | - type: textarea 48 | attributes: 49 | label: Describe alternatives you've considered 50 | 51 | - type: textarea 52 | attributes: 53 | label: Additional context/notes -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/processs_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Process Request 3 | about: Submit a process request to the library. Process requests are any requests related to library infrastructure, for example CI/CD, publishing, releasing, broken links. 4 | --- 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/questions.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: If you have a question, please use Discussions 4 | 5 | --- 6 | 7 | If you have a general question that goes beyond the library itself, we encourage you to use [Discussions](https://github.com//discussions) 8 | to engage with fellow community members! 9 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: 2 | - [ ] Make sure to open an issue as a [bug/issue](https://github.com/googleapis/nodejs-logging-bunyan/issues/new/choose) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea 3 | - [ ] Ensure the tests and linter pass 4 | - [ ] Code coverage does not decrease (if any source code was changed) 5 | - [ ] Appropriate docs were updated (if necessary) 6 | 7 | Fixes # 🦕 8 | -------------------------------------------------------------------------------- /.github/auto-approve.yml: -------------------------------------------------------------------------------- 1 | processes: 2 | - "NodeDependency" -------------------------------------------------------------------------------- /.github/auto-label.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | requestsize: 15 | enabled: true 16 | staleness: 17 | pullrequest: true 18 | old: 30 19 | extraold: 60 20 | -------------------------------------------------------------------------------- /.github/blunderbuss.yml: -------------------------------------------------------------------------------- 1 | assign_issues: 2 | - googleapis/api-logging-nodejs-reviewers 3 | assign_prs: 4 | - googleapis/api-logging-nodejs-reviewers 5 | -------------------------------------------------------------------------------- /.github/flakybot.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | issuePriority: p2 16 | 17 | -------------------------------------------------------------------------------- /.github/generated-files-bot.yml: -------------------------------------------------------------------------------- 1 | generatedFiles: 2 | - path: '.kokoro/**' 3 | message: '`.kokoro` files are templated and should be updated in [`synthtool`](https://github.com/googleapis/synthtool)' 4 | - path: '.github/CODEOWNERS' 5 | message: 'CODEOWNERS should instead be modified via the `codeowner_team` property in .repo-metadata.json' 6 | - path: '.github/workflows/ci.yaml' 7 | message: '`.github/workflows/ci.yaml` (GitHub Actions) should be updated in [`synthtool`](https://github.com/googleapis/synthtool)' 8 | - path: '.github/generated-files-bot.+(yml|yaml)' 9 | message: '`.github/generated-files-bot.(yml|yaml)` should be updated in [`synthtool`](https://github.com/googleapis/synthtool)' 10 | - path: 'README.md' 11 | message: '`README.md` is managed by [`synthtool`](https://github.com/googleapis/synthtool). However, a partials file can be used to update the README, e.g.: https://github.com/googleapis/nodejs-storage/blob/main/.readme-partials.yaml' 12 | - path: 'samples/README.md' 13 | message: '`samples/README.md` is managed by [`synthtool`](https://github.com/googleapis/synthtool). However, a partials file can be used to update the README, e.g.: https://github.com/googleapis/nodejs-storage/blob/main/.readme-partials.yaml' 14 | ignoreAuthors: 15 | - 'gcf-owl-bot[bot]' 16 | - 'yoshi-automation' 17 | -------------------------------------------------------------------------------- /.github/release-please.yml: -------------------------------------------------------------------------------- 1 | handleGHRelease: true 2 | releaseType: node 3 | extraFiles: ["src/index.ts"] 4 | -------------------------------------------------------------------------------- /.github/release-trigger.yml: -------------------------------------------------------------------------------- 1 | enabled: true 2 | -------------------------------------------------------------------------------- /.github/scripts/close-invalid-link.cjs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | async function closeIssue(github, owner, repo, number) { 16 | await github.rest.issues.createComment({ 17 | owner: owner, 18 | repo: repo, 19 | issue_number: number, 20 | body: 'Issue was opened with an invalid reproduction link. Please make sure the repository is a valid, publicly-accessible github repository, and make sure the url is complete (example: https://github.com/googleapis/google-cloud-node)' 21 | }); 22 | await github.rest.issues.update({ 23 | owner: owner, 24 | repo: repo, 25 | issue_number: number, 26 | state: 'closed' 27 | }); 28 | } 29 | module.exports = async ({github, context}) => { 30 | const owner = context.repo.owner; 31 | const repo = context.repo.repo; 32 | const number = context.issue.number; 33 | 34 | const issue = await github.rest.issues.get({ 35 | owner: owner, 36 | repo: repo, 37 | issue_number: number, 38 | }); 39 | 40 | const isBugTemplate = issue.data.body.includes('Link to the code that reproduces this issue'); 41 | 42 | if (isBugTemplate) { 43 | console.log(`Issue ${number} is a bug template`) 44 | try { 45 | const link = issue.data.body.split('\n')[18].match(/(https?:\/\/(gist\.)?github.com\/.*)/)[0]; 46 | console.log(`Issue ${number} contains this link: ${link}`) 47 | const isValidLink = (await fetch(link)).ok; 48 | console.log(`Issue ${number} has a ${isValidLink ? 'valid' : 'invalid'} link`) 49 | if (!isValidLink) { 50 | await closeIssue(github, owner, repo, number); 51 | } 52 | } catch (err) { 53 | await closeIssue(github, owner, repo, number); 54 | } 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /.github/scripts/close-unresponsive.cjs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | function labeledEvent(data) { 16 | return data.event === 'labeled' && data.label.name === 'needs more info'; 17 | } 18 | 19 | const numberOfDaysLimit = 15; 20 | const close_message = `This has been closed since a request for information has \ 21 | not been answered for ${numberOfDaysLimit} days. It can be reopened when the \ 22 | requested information is provided.`; 23 | 24 | module.exports = async ({github, context}) => { 25 | const owner = context.repo.owner; 26 | const repo = context.repo.repo; 27 | 28 | const issues = await github.rest.issues.listForRepo({ 29 | owner: owner, 30 | repo: repo, 31 | labels: 'needs more info', 32 | }); 33 | const numbers = issues.data.map((e) => e.number); 34 | 35 | for (const number of numbers) { 36 | const events = await github.paginate( 37 | github.rest.issues.listEventsForTimeline, 38 | { 39 | owner: owner, 40 | repo: repo, 41 | issue_number: number, 42 | }, 43 | (response) => response.data.filter(labeledEvent) 44 | ); 45 | 46 | const latest_response_label = events[events.length - 1]; 47 | 48 | const created_at = new Date(latest_response_label.created_at); 49 | const now = new Date(); 50 | const diff = now - created_at; 51 | const diffDays = diff / (1000 * 60 * 60 * 24); 52 | 53 | if (diffDays > numberOfDaysLimit) { 54 | await github.rest.issues.update({ 55 | owner: owner, 56 | repo: repo, 57 | issue_number: number, 58 | state: 'closed', 59 | }); 60 | 61 | await github.rest.issues.createComment({ 62 | owner: owner, 63 | repo: repo, 64 | issue_number: number, 65 | body: close_message, 66 | }); 67 | } 68 | } 69 | }; 70 | -------------------------------------------------------------------------------- /.github/scripts/remove-response-label.cjs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | module.exports = async ({ github, context }) => { 16 | const commenter = context.actor; 17 | const issue = await github.rest.issues.get({ 18 | owner: context.repo.owner, 19 | repo: context.repo.repo, 20 | issue_number: context.issue.number, 21 | }); 22 | const author = issue.data.user.login; 23 | const labels = issue.data.labels.map((e) => e.name); 24 | 25 | if (author === commenter && labels.includes('needs more info')) { 26 | await github.rest.issues.removeLabel({ 27 | owner: context.repo.owner, 28 | repo: context.repo.repo, 29 | issue_number: context.issue.number, 30 | name: 'needs more info', 31 | }); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /.github/sync-repo-settings.yaml: -------------------------------------------------------------------------------- 1 | branchProtectionRules: 2 | - pattern: main 3 | isAdminEnforced: true 4 | requiredApprovingReviewCount: 1 5 | requiresCodeOwnerReviews: true 6 | requiresStrictStatusChecks: true 7 | requiredStatusCheckContexts: 8 | - "ci/kokoro: Samples test" 9 | - "ci/kokoro: System test" 10 | - docs 11 | - lint 12 | - test (14) 13 | - test (16) 14 | - test (18) 15 | - cla/google 16 | - windows 17 | - OwlBot Post Processor 18 | permissionRules: 19 | - team: yoshi-admins 20 | permission: admin 21 | - team: jsteam-admins 22 | permission: admin 23 | - team: jsteam 24 | permission: push 25 | - team: api-logging-partners 26 | permission: push 27 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | pull_request: 6 | name: ci 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | node: [14, 16, 18, 20] 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version: ${{ matrix.node }} 18 | - run: node --version 19 | # The first installation step ensures that all of our production 20 | # dependencies work on the given Node.js version, this helps us find 21 | # dependencies that don't match our engines field: 22 | - run: npm install --production --engine-strict --ignore-scripts --no-package-lock 23 | # Clean up the production install, before installing dev/production: 24 | - run: rm -rf node_modules 25 | - run: npm install --engine-strict 26 | - run: npm test 27 | env: 28 | MOCHA_THROW_DEPRECATION: false 29 | windows: 30 | runs-on: windows-latest 31 | steps: 32 | - uses: actions/checkout@v3 33 | - uses: actions/setup-node@v3 34 | with: 35 | node-version: 14 36 | - run: npm install --engine-strict 37 | - run: npm test 38 | env: 39 | MOCHA_THROW_DEPRECATION: false 40 | lint: 41 | runs-on: ubuntu-latest 42 | steps: 43 | - uses: actions/checkout@v3 44 | - uses: actions/setup-node@v3 45 | with: 46 | node-version: 14 47 | - run: npm install 48 | - run: npm run lint 49 | docs: 50 | runs-on: ubuntu-latest 51 | steps: 52 | - uses: actions/checkout@v3 53 | - uses: actions/setup-node@v3 54 | with: 55 | node-version: 14 56 | - run: npm install 57 | - run: npm run docs 58 | - uses: JustinBeckwith/linkinator-action@v1 59 | with: 60 | paths: docs/ 61 | -------------------------------------------------------------------------------- /.github/workflows/issues-no-repro.yaml: -------------------------------------------------------------------------------- 1 | name: invalid_link 2 | on: 3 | issues: 4 | types: [opened, reopened] 5 | 6 | jobs: 7 | close: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | issues: write 11 | pull-requests: write 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: actions/github-script@v7 15 | with: 16 | script: | 17 | const script = require('./.github/scripts/close-invalid-link.cjs') 18 | await script({github, context}) 19 | -------------------------------------------------------------------------------- /.github/workflows/response.yaml: -------------------------------------------------------------------------------- 1 | name: no_response 2 | on: 3 | schedule: 4 | - cron: '30 1 * * *' # Run every day at 01:30 5 | workflow_dispatch: 6 | issue_comment: 7 | 8 | jobs: 9 | close: 10 | if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' 11 | runs-on: ubuntu-latest 12 | permissions: 13 | issues: write 14 | pull-requests: write 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: actions/github-script@v7 18 | with: 19 | script: | 20 | const script = require('./.github/scripts/close-unresponsive.cjs') 21 | await script({github, context}) 22 | 23 | remove_label: 24 | if: github.event_name == 'issue_comment' 25 | runs-on: ubuntu-latest 26 | permissions: 27 | issues: write 28 | pull-requests: write 29 | steps: 30 | - uses: actions/checkout@v4 31 | - uses: actions/github-script@v7 32 | with: 33 | script: | 34 | const script = require('./.github/scripts/remove-response-label.cjs') 35 | await script({github, context}) 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.log 2 | **/node_modules 3 | .coverage 4 | .nyc_output 5 | docs/ 6 | out/ 7 | build/ 8 | system-test/secrets.js 9 | system-test/*key.json 10 | *.lock 11 | .DS_Store 12 | google-cloud-logging-winston-*.tgz 13 | google-cloud-logging-bunyan-*.tgz 14 | package-lock.json 15 | key.json 16 | __pycache__ 17 | -------------------------------------------------------------------------------- /.jsdoc.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | 'use strict'; 17 | 18 | module.exports = { 19 | opts: { 20 | readme: './README.md', 21 | package: './package.json', 22 | template: './node_modules/jsdoc-fresh', 23 | recurse: true, 24 | verbose: true, 25 | destination: './docs/' 26 | }, 27 | plugins: [ 28 | 'plugins/markdown', 29 | 'jsdoc-region-tag' 30 | ], 31 | source: { 32 | excludePattern: '(^|\\/|\\\\)[._]', 33 | include: [ 34 | 'build/src' 35 | ], 36 | includePattern: '\\.js$' 37 | }, 38 | templates: { 39 | copyright: 'Copyright 2019 Google, LLC.', 40 | includeDate: false, 41 | sourceFiles: false, 42 | systemName: '@google-cloud/logging-bunyan', 43 | theme: 'lumen', 44 | default: { 45 | "outputSourceFiles": false 46 | } 47 | }, 48 | markdown: { 49 | idInHeadings: true 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /.kokoro/.gitattributes: -------------------------------------------------------------------------------- 1 | * linguist-generated=true 2 | -------------------------------------------------------------------------------- /.kokoro/common.cfg: -------------------------------------------------------------------------------- 1 | # Format: //devtools/kokoro/config/proto/build.proto 2 | 3 | # Build logs will be here 4 | action { 5 | define_artifacts { 6 | regex: "**/*sponge_log.xml" 7 | } 8 | } 9 | 10 | # Download trampoline resources. 11 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" 12 | 13 | # Use the trampoline script to run in docker. 14 | build_file: "nodejs-logging-bunyan/.kokoro/trampoline_v2.sh" 15 | 16 | # Configure the docker image for kokoro-trampoline. 17 | env_vars: { 18 | key: "TRAMPOLINE_IMAGE" 19 | value: "gcr.io/cloud-devrel-kokoro-resources/node:14-user" 20 | } 21 | env_vars: { 22 | key: "TRAMPOLINE_BUILD_FILE" 23 | value: "github/nodejs-logging-bunyan/.kokoro/test.sh" 24 | } 25 | 26 | 27 | ############################################# 28 | # this section merged from .kokoro/common_env_vars.cfg using owlbot.py 29 | 30 | env_vars: { 31 | key: "PRODUCT_AREA_LABEL" 32 | value: "observability" 33 | } 34 | env_vars: { 35 | key: "PRODUCT_LABEL" 36 | value: "logging" 37 | } 38 | env_vars: { 39 | key: "LANGUAGE_LABEL" 40 | value: "nodejs" 41 | } 42 | 43 | ################################################### 44 | 45 | -------------------------------------------------------------------------------- /.kokoro/common_env_vars.cfg: -------------------------------------------------------------------------------- 1 | 2 | ############################################# 3 | # this section merged from .kokoro/common_env_vars.cfg using owlbot.py 4 | 5 | env_vars: { 6 | key: "PRODUCT_AREA_LABEL" 7 | value: "observability" 8 | } 9 | env_vars: { 10 | key: "PRODUCT_LABEL" 11 | value: "logging" 12 | } 13 | env_vars: { 14 | key: "LANGUAGE_LABEL" 15 | value: "nodejs" 16 | } 17 | 18 | ################################################### 19 | 20 | -------------------------------------------------------------------------------- /.kokoro/continuous/node14/common.cfg: -------------------------------------------------------------------------------- 1 | # Format: //devtools/kokoro/config/proto/build.proto 2 | 3 | # Build logs will be here 4 | action { 5 | define_artifacts { 6 | regex: "**/*sponge_log.xml" 7 | } 8 | } 9 | 10 | # Download trampoline resources. 11 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" 12 | 13 | # Use the trampoline script to run in docker. 14 | build_file: "nodejs-logging-bunyan/.kokoro/trampoline_v2.sh" 15 | 16 | # Configure the docker image for kokoro-trampoline. 17 | env_vars: { 18 | key: "TRAMPOLINE_IMAGE" 19 | value: "gcr.io/cloud-devrel-kokoro-resources/node:14-user" 20 | } 21 | env_vars: { 22 | key: "TRAMPOLINE_BUILD_FILE" 23 | value: "github/nodejs-logging-bunyan/.kokoro/test.sh" 24 | } 25 | 26 | 27 | ############################################# 28 | # this section merged from .kokoro/common_env_vars.cfg using owlbot.py 29 | 30 | env_vars: { 31 | key: "PRODUCT_AREA_LABEL" 32 | value: "observability" 33 | } 34 | env_vars: { 35 | key: "PRODUCT_LABEL" 36 | value: "logging" 37 | } 38 | env_vars: { 39 | key: "LANGUAGE_LABEL" 40 | value: "nodejs" 41 | } 42 | 43 | ################################################### 44 | 45 | -------------------------------------------------------------------------------- /.kokoro/continuous/node14/lint.cfg: -------------------------------------------------------------------------------- 1 | env_vars: { 2 | key: "TRAMPOLINE_BUILD_FILE" 3 | value: "github/nodejs-logging-bunyan/.kokoro/lint.sh" 4 | } 5 | -------------------------------------------------------------------------------- /.kokoro/continuous/node14/samples-test.cfg: -------------------------------------------------------------------------------- 1 | # Download resources for system tests (service account key, etc.) 2 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-nodejs" 3 | 4 | env_vars: { 5 | key: "TRAMPOLINE_BUILD_FILE" 6 | value: "github/nodejs-logging-bunyan/.kokoro/samples-test.sh" 7 | } 8 | 9 | env_vars: { 10 | key: "SECRET_MANAGER_KEYS" 11 | value: "long-door-651-kokoro-system-test-service-account" 12 | } -------------------------------------------------------------------------------- /.kokoro/continuous/node14/system-test.cfg: -------------------------------------------------------------------------------- 1 | # Download resources for system tests (service account key, etc.) 2 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-nodejs" 3 | 4 | env_vars: { 5 | key: "TRAMPOLINE_BUILD_FILE" 6 | value: "github/nodejs-logging-bunyan/.kokoro/system-test.sh" 7 | } 8 | 9 | env_vars: { 10 | key: "SECRET_MANAGER_KEYS" 11 | value: "long-door-651-kokoro-system-test-service-account" 12 | } -------------------------------------------------------------------------------- /.kokoro/continuous/node14/test.cfg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/nodejs-logging-bunyan/34a278c724f9bfcf1511e22b7deb234007f713bf/.kokoro/continuous/node14/test.cfg -------------------------------------------------------------------------------- /.kokoro/docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -eo pipefail 18 | 19 | export NPM_CONFIG_PREFIX=${HOME}/.npm-global 20 | 21 | cd $(dirname $0)/.. 22 | 23 | npm install 24 | 25 | npm run docs-test 26 | -------------------------------------------------------------------------------- /.kokoro/lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -eo pipefail 18 | 19 | export NPM_CONFIG_PREFIX=${HOME}/.npm-global 20 | 21 | cd $(dirname $0)/.. 22 | 23 | npm install 24 | 25 | # Install and link samples 26 | if [ -f samples/package.json ]; then 27 | cd samples/ 28 | npm link ../ 29 | npm install 30 | cd .. 31 | fi 32 | 33 | npm run lint 34 | -------------------------------------------------------------------------------- /.kokoro/populate-secrets.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2020 Google LLC. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # This file is called in the early stage of `trampoline_v2.sh` to 17 | # populate secrets needed for the CI builds. 18 | 19 | set -eo pipefail 20 | 21 | function now { date +"%Y-%m-%d %H:%M:%S" | tr -d '\n' ;} 22 | function msg { println "$*" >&2 ;} 23 | function println { printf '%s\n' "$(now) $*" ;} 24 | 25 | # Populates requested secrets set in SECRET_MANAGER_KEYS 26 | 27 | # In Kokoro CI builds, we use the service account attached to the 28 | # Kokoro VM. This means we need to setup auth on other CI systems. 29 | # For local run, we just use the gcloud command for retrieving the 30 | # secrets. 31 | 32 | if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then 33 | GCLOUD_COMMANDS=( 34 | "docker" 35 | "run" 36 | "--entrypoint=gcloud" 37 | "--volume=${KOKORO_GFILE_DIR}:${KOKORO_GFILE_DIR}" 38 | "gcr.io/google.com/cloudsdktool/cloud-sdk" 39 | ) 40 | if [[ "${TRAMPOLINE_CI:-}" == "kokoro" ]]; then 41 | SECRET_LOCATION="${KOKORO_GFILE_DIR}/secret_manager" 42 | else 43 | echo "Authentication for this CI system is not implemented yet." 44 | exit 2 45 | # TODO: Determine appropriate SECRET_LOCATION and the GCLOUD_COMMANDS. 46 | fi 47 | else 48 | # For local run, use /dev/shm or temporary directory for 49 | # KOKORO_GFILE_DIR. 50 | if [[ -d "/dev/shm" ]]; then 51 | export KOKORO_GFILE_DIR=/dev/shm 52 | else 53 | export KOKORO_GFILE_DIR=$(mktemp -d -t ci-XXXXXXXX) 54 | fi 55 | SECRET_LOCATION="${KOKORO_GFILE_DIR}/secret_manager" 56 | GCLOUD_COMMANDS=("gcloud") 57 | fi 58 | 59 | msg "Creating folder on disk for secrets: ${SECRET_LOCATION}" 60 | mkdir -p ${SECRET_LOCATION} 61 | 62 | for key in $(echo ${SECRET_MANAGER_KEYS} | sed "s/,/ /g") 63 | do 64 | msg "Retrieving secret ${key}" 65 | "${GCLOUD_COMMANDS[@]}" \ 66 | secrets versions access latest \ 67 | --project cloud-devrel-kokoro-resources \ 68 | --secret $key > \ 69 | "$SECRET_LOCATION/$key" 70 | if [[ $? == 0 ]]; then 71 | msg "Secret written to ${SECRET_LOCATION}/${key}" 72 | else 73 | msg "Error retrieving secret ${key}" 74 | exit 2 75 | fi 76 | done 77 | -------------------------------------------------------------------------------- /.kokoro/presubmit/node14/common.cfg: -------------------------------------------------------------------------------- 1 | # Format: //devtools/kokoro/config/proto/build.proto 2 | 3 | # Build logs will be here 4 | action { 5 | define_artifacts { 6 | regex: "**/*sponge_log.xml" 7 | } 8 | } 9 | 10 | # Download trampoline resources. 11 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" 12 | 13 | # Use the trampoline script to run in docker. 14 | build_file: "nodejs-logging-bunyan/.kokoro/trampoline_v2.sh" 15 | 16 | # Configure the docker image for kokoro-trampoline. 17 | env_vars: { 18 | key: "TRAMPOLINE_IMAGE" 19 | value: "gcr.io/cloud-devrel-kokoro-resources/node:14-user" 20 | } 21 | env_vars: { 22 | key: "TRAMPOLINE_BUILD_FILE" 23 | value: "github/nodejs-logging-bunyan/.kokoro/test.sh" 24 | } 25 | -------------------------------------------------------------------------------- /.kokoro/presubmit/node14/samples-test.cfg: -------------------------------------------------------------------------------- 1 | # Download resources for system tests (service account key, etc.) 2 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-nodejs" 3 | 4 | env_vars: { 5 | key: "TRAMPOLINE_BUILD_FILE" 6 | value: "github/nodejs-logging-bunyan/.kokoro/samples-test.sh" 7 | } 8 | 9 | env_vars: { 10 | key: "SECRET_MANAGER_KEYS" 11 | value: "long-door-651-kokoro-system-test-service-account" 12 | } -------------------------------------------------------------------------------- /.kokoro/presubmit/node14/system-test.cfg: -------------------------------------------------------------------------------- 1 | # Download resources for system tests (service account key, etc.) 2 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-nodejs" 3 | 4 | env_vars: { 5 | key: "TRAMPOLINE_BUILD_FILE" 6 | value: "github/nodejs-logging-bunyan/.kokoro/system-test.sh" 7 | } 8 | 9 | env_vars: { 10 | key: "SECRET_MANAGER_KEYS" 11 | value: "long-door-651-kokoro-system-test-service-account" 12 | } -------------------------------------------------------------------------------- /.kokoro/presubmit/node14/test.cfg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/nodejs-logging-bunyan/34a278c724f9bfcf1511e22b7deb234007f713bf/.kokoro/presubmit/node14/test.cfg -------------------------------------------------------------------------------- /.kokoro/presubmit/windows/common.cfg: -------------------------------------------------------------------------------- 1 | # Format: //devtools/kokoro/config/proto/build.proto 2 | 3 | -------------------------------------------------------------------------------- /.kokoro/presubmit/windows/test.cfg: -------------------------------------------------------------------------------- 1 | # Use the test file directly 2 | build_file: "nodejs-logging-bunyan/.kokoro/test.bat" 3 | -------------------------------------------------------------------------------- /.kokoro/publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -eo pipefail 18 | 19 | export NPM_CONFIG_PREFIX=${HOME}/.npm-global 20 | 21 | # Start the releasetool reporter 22 | python3 -m releasetool publish-reporter-script > /tmp/publisher-script; source /tmp/publisher-script 23 | 24 | cd $(dirname $0)/.. 25 | 26 | NPM_TOKEN=$(cat $KOKORO_KEYSTORE_DIR/73713_google-cloud-npm-token-1) 27 | echo "//wombat-dressing-room.appspot.com/:_authToken=${NPM_TOKEN}" > ~/.npmrc 28 | 29 | npm install 30 | npm pack . 31 | # npm provides no way to specify, observe, or predict the name of the tarball 32 | # file it generates. We have to look in the current directory for the freshest 33 | # .tgz file. 34 | TARBALL=$(ls -1 -t *.tgz | head -1) 35 | 36 | npm publish --access=public --registry=https://wombat-dressing-room.appspot.com "$TARBALL" 37 | 38 | # Kokoro collects *.tgz and package-lock.json files and stores them in Placer 39 | # so we can generate SBOMs and attestations. 40 | # However, we *don't* want Kokoro to collect package-lock.json and *.tgz files 41 | # that happened to be installed with dependencies. 42 | find node_modules -name package-lock.json -o -name "*.tgz" | xargs rm -f -------------------------------------------------------------------------------- /.kokoro/release/common.cfg: -------------------------------------------------------------------------------- 1 | before_action { 2 | fetch_keystore { 3 | keystore_resource { 4 | keystore_config_id: 73713 5 | keyname: "yoshi-automation-github-key" 6 | } 7 | } 8 | } 9 | 10 | env_vars: { 11 | key: "PRODUCT_AREA_LABEL" 12 | value: "observability" 13 | } 14 | env_vars: { 15 | key: "PRODUCT_LABEL" 16 | value: "logging" 17 | } 18 | env_vars: { 19 | key: "LANGUAGE_LABEL" 20 | value: "nodejs" 21 | } 22 | -------------------------------------------------------------------------------- /.kokoro/release/docs-devsite.cfg: -------------------------------------------------------------------------------- 1 | # service account used to publish up-to-date docs. 2 | before_action { 3 | fetch_keystore { 4 | keystore_resource { 5 | keystore_config_id: 73713 6 | keyname: "docuploader_service_account" 7 | } 8 | } 9 | } 10 | 11 | # doc publications use a Python image. 12 | env_vars: { 13 | key: "TRAMPOLINE_IMAGE" 14 | value: "gcr.io/cloud-devrel-kokoro-resources/node:14-user" 15 | } 16 | 17 | # Download trampoline resources. 18 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" 19 | 20 | # Use the trampoline script to run in docker. 21 | build_file: "nodejs-logging-bunyan/.kokoro/trampoline_v2.sh" 22 | 23 | env_vars: { 24 | key: "TRAMPOLINE_BUILD_FILE" 25 | value: "github/nodejs-logging-bunyan/.kokoro/release/docs-devsite.sh" 26 | } 27 | -------------------------------------------------------------------------------- /.kokoro/release/docs-devsite.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2021 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -eo pipefail 18 | 19 | if [[ -z "$CREDENTIALS" ]]; then 20 | # if CREDENTIALS are explicitly set, assume we're testing locally 21 | # and don't set NPM_CONFIG_PREFIX. 22 | export NPM_CONFIG_PREFIX=${HOME}/.npm-global 23 | export PATH="$PATH:${NPM_CONFIG_PREFIX}/bin" 24 | cd $(dirname $0)/../.. 25 | fi 26 | 27 | npm install 28 | npm install --no-save @google-cloud/cloud-rad@^0.4.0 29 | # publish docs to devsite 30 | npx @google-cloud/cloud-rad . cloud-rad 31 | -------------------------------------------------------------------------------- /.kokoro/release/docs.cfg: -------------------------------------------------------------------------------- 1 | # service account used to publish up-to-date docs. 2 | before_action { 3 | fetch_keystore { 4 | keystore_resource { 5 | keystore_config_id: 73713 6 | keyname: "docuploader_service_account" 7 | } 8 | } 9 | } 10 | 11 | # doc publications use a Python image. 12 | env_vars: { 13 | key: "TRAMPOLINE_IMAGE" 14 | value: "gcr.io/cloud-devrel-kokoro-resources/node:14-user" 15 | } 16 | 17 | # Download trampoline resources. 18 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" 19 | 20 | # Use the trampoline script to run in docker. 21 | build_file: "nodejs-logging-bunyan/.kokoro/trampoline_v2.sh" 22 | 23 | env_vars: { 24 | key: "TRAMPOLINE_BUILD_FILE" 25 | value: "github/nodejs-logging-bunyan/.kokoro/release/docs.sh" 26 | } 27 | -------------------------------------------------------------------------------- /.kokoro/release/docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2019 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -eo pipefail 18 | 19 | # build jsdocs (Python is installed on the Node 10 docker image). 20 | if [[ -z "$CREDENTIALS" ]]; then 21 | # if CREDENTIALS are explicitly set, assume we're testing locally 22 | # and don't set NPM_CONFIG_PREFIX. 23 | export NPM_CONFIG_PREFIX=${HOME}/.npm-global 24 | export PATH="$PATH:${NPM_CONFIG_PREFIX}/bin" 25 | cd $(dirname $0)/../.. 26 | fi 27 | npm install 28 | npm run docs 29 | 30 | # create docs.metadata, based on package.json and .repo-metadata.json. 31 | npm i json@9.0.6 -g 32 | python3 -m docuploader create-metadata \ 33 | --name=$(cat .repo-metadata.json | json name) \ 34 | --version=$(cat package.json | json version) \ 35 | --language=$(cat .repo-metadata.json | json language) \ 36 | --distribution-name=$(cat .repo-metadata.json | json distribution_name) \ 37 | --product-page=$(cat .repo-metadata.json | json product_documentation) \ 38 | --github-repository=$(cat .repo-metadata.json | json repo) \ 39 | --issue-tracker=$(cat .repo-metadata.json | json issue_tracker) 40 | cp docs.metadata ./docs/docs.metadata 41 | 42 | # deploy the docs. 43 | if [[ -z "$CREDENTIALS" ]]; then 44 | CREDENTIALS=${KOKORO_KEYSTORE_DIR}/73713_docuploader_service_account 45 | fi 46 | if [[ -z "$BUCKET" ]]; then 47 | BUCKET=docs-staging 48 | fi 49 | python3 -m docuploader upload ./docs --credentials $CREDENTIALS --staging-bucket $BUCKET 50 | -------------------------------------------------------------------------------- /.kokoro/release/publish.cfg: -------------------------------------------------------------------------------- 1 | before_action { 2 | fetch_keystore { 3 | keystore_resource { 4 | keystore_config_id: 73713 5 | keyname: "docuploader_service_account" 6 | } 7 | } 8 | } 9 | 10 | before_action { 11 | fetch_keystore { 12 | keystore_resource { 13 | keystore_config_id: 73713 14 | keyname: "google-cloud-npm-token-1" 15 | } 16 | } 17 | } 18 | 19 | env_vars: { 20 | key: "SECRET_MANAGER_KEYS" 21 | value: "releasetool-publish-reporter-app,releasetool-publish-reporter-googleapis-installation,releasetool-publish-reporter-pem" 22 | } 23 | 24 | # Download trampoline resources. 25 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" 26 | 27 | # Use the trampoline script to run in docker. 28 | build_file: "nodejs-logging-bunyan/.kokoro/trampoline_v2.sh" 29 | 30 | # Configure the docker image for kokoro-trampoline. 31 | env_vars: { 32 | key: "TRAMPOLINE_IMAGE" 33 | value: "gcr.io/cloud-devrel-kokoro-resources/node:14-user" 34 | } 35 | 36 | env_vars: { 37 | key: "TRAMPOLINE_BUILD_FILE" 38 | value: "github/nodejs-logging-bunyan/.kokoro/publish.sh" 39 | } 40 | 41 | # Store the packages we uploaded to npmjs.org and their corresponding 42 | # package-lock.jsons in Placer. That way, we have a record of exactly 43 | # what we published, and which version of which tools we used to publish 44 | # it, which we can use to generate SBOMs and attestations. 45 | action { 46 | define_artifacts { 47 | regex: "github/**/*.tgz" 48 | regex: "github/**/package-lock.json" 49 | strip_prefix: "github" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /.kokoro/samples-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -eo pipefail 18 | 19 | export NPM_CONFIG_PREFIX=${HOME}/.npm-global 20 | 21 | # Setup service account credentials. 22 | export GOOGLE_APPLICATION_CREDENTIALS=${KOKORO_GFILE_DIR}/secret_manager/long-door-651-kokoro-system-test-service-account 23 | export GCLOUD_PROJECT=long-door-651 24 | 25 | cd $(dirname $0)/.. 26 | 27 | # Run a pre-test hook, if a pre-samples-test.sh is in the project 28 | if [ -f .kokoro/pre-samples-test.sh ]; then 29 | set +x 30 | . .kokoro/pre-samples-test.sh 31 | set -x 32 | fi 33 | 34 | if [ -f samples/package.json ]; then 35 | npm install 36 | 37 | # Install and link samples 38 | cd samples/ 39 | npm link ../ 40 | npm install 41 | cd .. 42 | # If tests are running against main branch, configure flakybot 43 | # to open issues on failures: 44 | if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"continuous"* ]] || [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"nightly"* ]]; then 45 | export MOCHA_REPORTER_OUTPUT=test_output_sponge_log.xml 46 | export MOCHA_REPORTER=xunit 47 | cleanup() { 48 | chmod +x $KOKORO_GFILE_DIR/linux_amd64/flakybot 49 | $KOKORO_GFILE_DIR/linux_amd64/flakybot 50 | } 51 | trap cleanup EXIT HUP 52 | fi 53 | 54 | npm run samples-test 55 | fi 56 | 57 | # codecov combines coverage across integration and unit tests. Include 58 | # the logic below for any environment you wish to collect coverage for: 59 | COVERAGE_NODE=14 60 | if npx check-node-version@3.3.0 --silent --node $COVERAGE_NODE; then 61 | NYC_BIN=./node_modules/nyc/bin/nyc.js 62 | if [ -f "$NYC_BIN" ]; then 63 | $NYC_BIN report || true 64 | fi 65 | bash $KOKORO_GFILE_DIR/codecov.sh 66 | else 67 | echo "coverage is only reported for Node $COVERAGE_NODE" 68 | fi 69 | -------------------------------------------------------------------------------- /.kokoro/system-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -eo pipefail 18 | 19 | export NPM_CONFIG_PREFIX=${HOME}/.npm-global 20 | 21 | # Setup service account credentials. 22 | export GOOGLE_APPLICATION_CREDENTIALS=${KOKORO_GFILE_DIR}/secret_manager/long-door-651-kokoro-system-test-service-account 23 | export GCLOUD_PROJECT=long-door-651 24 | 25 | cd $(dirname $0)/.. 26 | 27 | # Run a pre-test hook, if a pre-system-test.sh is in the project 28 | if [ -f .kokoro/pre-system-test.sh ]; then 29 | set +x 30 | . .kokoro/pre-system-test.sh 31 | set -x 32 | fi 33 | 34 | npm install 35 | 36 | # If tests are running against main branch, configure flakybot 37 | # to open issues on failures: 38 | if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"continuous"* ]] || [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"nightly"* ]]; then 39 | export MOCHA_REPORTER_OUTPUT=test_output_sponge_log.xml 40 | export MOCHA_REPORTER=xunit 41 | cleanup() { 42 | chmod +x $KOKORO_GFILE_DIR/linux_amd64/flakybot 43 | $KOKORO_GFILE_DIR/linux_amd64/flakybot 44 | } 45 | trap cleanup EXIT HUP 46 | fi 47 | 48 | npm run system-test 49 | 50 | # codecov combines coverage across integration and unit tests. Include 51 | # the logic below for any environment you wish to collect coverage for: 52 | COVERAGE_NODE=14 53 | if npx check-node-version@3.3.0 --silent --node $COVERAGE_NODE; then 54 | NYC_BIN=./node_modules/nyc/bin/nyc.js 55 | if [ -f "$NYC_BIN" ]; then 56 | $NYC_BIN report || true 57 | fi 58 | bash $KOKORO_GFILE_DIR/codecov.sh 59 | else 60 | echo "coverage is only reported for Node $COVERAGE_NODE" 61 | fi 62 | -------------------------------------------------------------------------------- /.kokoro/test.bat: -------------------------------------------------------------------------------- 1 | @rem Copyright 2018 Google LLC. All rights reserved. 2 | @rem 3 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 4 | @rem you may not use this file except in compliance with the License. 5 | @rem You may obtain a copy of the License at 6 | @rem 7 | @rem http://www.apache.org/licenses/LICENSE-2.0 8 | @rem 9 | @rem Unless required by applicable law or agreed to in writing, software 10 | @rem distributed under the License is distributed on an "AS IS" BASIS, 11 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | @rem See the License for the specific language governing permissions and 13 | @rem limitations under the License. 14 | 15 | @echo "Starting Windows build" 16 | 17 | cd /d %~dp0 18 | cd .. 19 | 20 | @rem npm path is not currently set in our image, we should fix this next time 21 | @rem we upgrade Node.js in the image: 22 | SET PATH=%PATH%;/cygdrive/c/Program Files/nodejs/npm 23 | 24 | call nvm use v14.17.3 25 | call which node 26 | 27 | call npm install || goto :error 28 | call npm run test || goto :error 29 | 30 | goto :EOF 31 | 32 | :error 33 | exit /b 1 34 | -------------------------------------------------------------------------------- /.kokoro/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -eo pipefail 18 | 19 | export NPM_CONFIG_PREFIX=${HOME}/.npm-global 20 | 21 | cd $(dirname $0)/.. 22 | 23 | npm install 24 | # If tests are running against main branch, configure flakybot 25 | # to open issues on failures: 26 | if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"continuous"* ]] || [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"nightly"* ]]; then 27 | export MOCHA_REPORTER_OUTPUT=test_output_sponge_log.xml 28 | export MOCHA_REPORTER=xunit 29 | cleanup() { 30 | chmod +x $KOKORO_GFILE_DIR/linux_amd64/flakybot 31 | $KOKORO_GFILE_DIR/linux_amd64/flakybot 32 | } 33 | trap cleanup EXIT HUP 34 | fi 35 | # Unit tests exercise the entire API surface, which may include 36 | # deprecation warnings: 37 | export MOCHA_THROW_DEPRECATION=false 38 | npm test 39 | 40 | # codecov combines coverage across integration and unit tests. Include 41 | # the logic below for any environment you wish to collect coverage for: 42 | COVERAGE_NODE=14 43 | if npx check-node-version@3.3.0 --silent --node $COVERAGE_NODE; then 44 | NYC_BIN=./node_modules/nyc/bin/nyc.js 45 | if [ -f "$NYC_BIN" ]; then 46 | $NYC_BIN report || true 47 | fi 48 | bash $KOKORO_GFILE_DIR/codecov.sh 49 | else 50 | echo "coverage is only reported for Node $COVERAGE_NODE" 51 | fi 52 | -------------------------------------------------------------------------------- /.kokoro/trampoline.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2017 Google Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # This file is not used any more, but we keep this file for making it 17 | # easy to roll back. 18 | # TODO: Remove this file from the template. 19 | 20 | set -eo pipefail 21 | 22 | # Always run the cleanup script, regardless of the success of bouncing into 23 | # the container. 24 | function cleanup() { 25 | chmod +x ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh 26 | ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh 27 | echo "cleanup"; 28 | } 29 | trap cleanup EXIT 30 | 31 | $(dirname $0)/populate-secrets.sh # Secret Manager secrets. 32 | python3 "${KOKORO_GFILE_DIR}/trampoline_v1.py" 33 | -------------------------------------------------------------------------------- /.kokoro/trampoline_v2.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright 2020 Google LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # trampoline_v2.sh 17 | # 18 | # If you want to make a change to this file, consider doing so at: 19 | # https://github.com/googlecloudplatform/docker-ci-helper 20 | # 21 | # This script is for running CI builds. For Kokoro builds, we 22 | # set this script to `build_file` field in the Kokoro configuration. 23 | 24 | # This script does 3 things. 25 | # 26 | # 1. Prepare the Docker image for the test 27 | # 2. Run the Docker with appropriate flags to run the test 28 | # 3. Upload the newly built Docker image 29 | # 30 | # in a way that is somewhat compatible with trampoline_v1. 31 | # 32 | # These environment variables are required: 33 | # TRAMPOLINE_IMAGE: The docker image to use. 34 | # TRAMPOLINE_DOCKERFILE: The location of the Dockerfile. 35 | # 36 | # You can optionally change these environment variables: 37 | # TRAMPOLINE_IMAGE_UPLOAD: 38 | # (true|false): Whether to upload the Docker image after the 39 | # successful builds. 40 | # TRAMPOLINE_BUILD_FILE: The script to run in the docker container. 41 | # TRAMPOLINE_WORKSPACE: The workspace path in the docker container. 42 | # Defaults to /workspace. 43 | # Potentially there are some repo specific envvars in .trampolinerc in 44 | # the project root. 45 | # 46 | # Here is an example for running this script. 47 | # TRAMPOLINE_IMAGE=gcr.io/cloud-devrel-kokoro-resources/node:10-user \ 48 | # TRAMPOLINE_BUILD_FILE=.kokoro/system-test.sh \ 49 | # .kokoro/trampoline_v2.sh 50 | 51 | set -euo pipefail 52 | 53 | TRAMPOLINE_VERSION="2.0.7" 54 | 55 | if command -v tput >/dev/null && [[ -n "${TERM:-}" ]]; then 56 | readonly IO_COLOR_RED="$(tput setaf 1)" 57 | readonly IO_COLOR_GREEN="$(tput setaf 2)" 58 | readonly IO_COLOR_YELLOW="$(tput setaf 3)" 59 | readonly IO_COLOR_RESET="$(tput sgr0)" 60 | else 61 | readonly IO_COLOR_RED="" 62 | readonly IO_COLOR_GREEN="" 63 | readonly IO_COLOR_YELLOW="" 64 | readonly IO_COLOR_RESET="" 65 | fi 66 | 67 | function function_exists { 68 | [ $(LC_ALL=C type -t $1)"" == "function" ] 69 | } 70 | 71 | # Logs a message using the given color. The first argument must be one 72 | # of the IO_COLOR_* variables defined above, such as 73 | # "${IO_COLOR_YELLOW}". The remaining arguments will be logged in the 74 | # given color. The log message will also have an RFC-3339 timestamp 75 | # prepended (in UTC). You can disable the color output by setting 76 | # TERM=vt100. 77 | function log_impl() { 78 | local color="$1" 79 | shift 80 | local timestamp="$(date -u "+%Y-%m-%dT%H:%M:%SZ")" 81 | echo "================================================================" 82 | echo "${color}${timestamp}:" "$@" "${IO_COLOR_RESET}" 83 | echo "================================================================" 84 | } 85 | 86 | # Logs the given message with normal coloring and a timestamp. 87 | function log() { 88 | log_impl "${IO_COLOR_RESET}" "$@" 89 | } 90 | 91 | # Logs the given message in green with a timestamp. 92 | function log_green() { 93 | log_impl "${IO_COLOR_GREEN}" "$@" 94 | } 95 | 96 | # Logs the given message in yellow with a timestamp. 97 | function log_yellow() { 98 | log_impl "${IO_COLOR_YELLOW}" "$@" 99 | } 100 | 101 | # Logs the given message in red with a timestamp. 102 | function log_red() { 103 | log_impl "${IO_COLOR_RED}" "$@" 104 | } 105 | 106 | readonly tmpdir=$(mktemp -d -t ci-XXXXXXXX) 107 | readonly tmphome="${tmpdir}/h" 108 | mkdir -p "${tmphome}" 109 | 110 | function cleanup() { 111 | rm -rf "${tmpdir}" 112 | } 113 | trap cleanup EXIT 114 | 115 | RUNNING_IN_CI="${RUNNING_IN_CI:-false}" 116 | 117 | # The workspace in the container, defaults to /workspace. 118 | TRAMPOLINE_WORKSPACE="${TRAMPOLINE_WORKSPACE:-/workspace}" 119 | 120 | pass_down_envvars=( 121 | # TRAMPOLINE_V2 variables. 122 | # Tells scripts whether they are running as part of CI or not. 123 | "RUNNING_IN_CI" 124 | # Indicates which CI system we're in. 125 | "TRAMPOLINE_CI" 126 | # Indicates the version of the script. 127 | "TRAMPOLINE_VERSION" 128 | # Contains path to build artifacts being executed. 129 | "KOKORO_BUILD_ARTIFACTS_SUBDIR" 130 | ) 131 | 132 | log_yellow "Building with Trampoline ${TRAMPOLINE_VERSION}" 133 | 134 | # Detect which CI systems we're in. If we're in any of the CI systems 135 | # we support, `RUNNING_IN_CI` will be true and `TRAMPOLINE_CI` will be 136 | # the name of the CI system. Both envvars will be passing down to the 137 | # container for telling which CI system we're in. 138 | if [[ -n "${KOKORO_BUILD_ID:-}" ]]; then 139 | # descriptive env var for indicating it's on CI. 140 | RUNNING_IN_CI="true" 141 | TRAMPOLINE_CI="kokoro" 142 | if [[ "${TRAMPOLINE_USE_LEGACY_SERVICE_ACCOUNT:-}" == "true" ]]; then 143 | if [[ ! -f "${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json" ]]; then 144 | log_red "${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json does not exist. Did you forget to mount cloud-devrel-kokoro-resources/trampoline? Aborting." 145 | exit 1 146 | fi 147 | # This service account will be activated later. 148 | TRAMPOLINE_SERVICE_ACCOUNT="${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json" 149 | else 150 | if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then 151 | gcloud auth list 152 | fi 153 | log_yellow "Configuring Container Registry access" 154 | gcloud auth configure-docker --quiet 155 | fi 156 | pass_down_envvars+=( 157 | # KOKORO dynamic variables. 158 | "KOKORO_BUILD_NUMBER" 159 | "KOKORO_BUILD_ID" 160 | "KOKORO_JOB_NAME" 161 | "KOKORO_GIT_COMMIT" 162 | "KOKORO_GITHUB_COMMIT" 163 | "KOKORO_GITHUB_PULL_REQUEST_NUMBER" 164 | "KOKORO_GITHUB_PULL_REQUEST_COMMIT" 165 | # For flakybot 166 | "KOKORO_GITHUB_COMMIT_URL" 167 | "KOKORO_GITHUB_PULL_REQUEST_URL" 168 | ) 169 | elif [[ "${TRAVIS:-}" == "true" ]]; then 170 | RUNNING_IN_CI="true" 171 | TRAMPOLINE_CI="travis" 172 | pass_down_envvars+=( 173 | "TRAVIS_BRANCH" 174 | "TRAVIS_BUILD_ID" 175 | "TRAVIS_BUILD_NUMBER" 176 | "TRAVIS_BUILD_WEB_URL" 177 | "TRAVIS_COMMIT" 178 | "TRAVIS_COMMIT_MESSAGE" 179 | "TRAVIS_COMMIT_RANGE" 180 | "TRAVIS_JOB_NAME" 181 | "TRAVIS_JOB_NUMBER" 182 | "TRAVIS_JOB_WEB_URL" 183 | "TRAVIS_PULL_REQUEST" 184 | "TRAVIS_PULL_REQUEST_BRANCH" 185 | "TRAVIS_PULL_REQUEST_SHA" 186 | "TRAVIS_PULL_REQUEST_SLUG" 187 | "TRAVIS_REPO_SLUG" 188 | "TRAVIS_SECURE_ENV_VARS" 189 | "TRAVIS_TAG" 190 | ) 191 | elif [[ -n "${GITHUB_RUN_ID:-}" ]]; then 192 | RUNNING_IN_CI="true" 193 | TRAMPOLINE_CI="github-workflow" 194 | pass_down_envvars+=( 195 | "GITHUB_WORKFLOW" 196 | "GITHUB_RUN_ID" 197 | "GITHUB_RUN_NUMBER" 198 | "GITHUB_ACTION" 199 | "GITHUB_ACTIONS" 200 | "GITHUB_ACTOR" 201 | "GITHUB_REPOSITORY" 202 | "GITHUB_EVENT_NAME" 203 | "GITHUB_EVENT_PATH" 204 | "GITHUB_SHA" 205 | "GITHUB_REF" 206 | "GITHUB_HEAD_REF" 207 | "GITHUB_BASE_REF" 208 | ) 209 | elif [[ "${CIRCLECI:-}" == "true" ]]; then 210 | RUNNING_IN_CI="true" 211 | TRAMPOLINE_CI="circleci" 212 | pass_down_envvars+=( 213 | "CIRCLE_BRANCH" 214 | "CIRCLE_BUILD_NUM" 215 | "CIRCLE_BUILD_URL" 216 | "CIRCLE_COMPARE_URL" 217 | "CIRCLE_JOB" 218 | "CIRCLE_NODE_INDEX" 219 | "CIRCLE_NODE_TOTAL" 220 | "CIRCLE_PREVIOUS_BUILD_NUM" 221 | "CIRCLE_PROJECT_REPONAME" 222 | "CIRCLE_PROJECT_USERNAME" 223 | "CIRCLE_REPOSITORY_URL" 224 | "CIRCLE_SHA1" 225 | "CIRCLE_STAGE" 226 | "CIRCLE_USERNAME" 227 | "CIRCLE_WORKFLOW_ID" 228 | "CIRCLE_WORKFLOW_JOB_ID" 229 | "CIRCLE_WORKFLOW_UPSTREAM_JOB_IDS" 230 | "CIRCLE_WORKFLOW_WORKSPACE_ID" 231 | ) 232 | fi 233 | 234 | # Configure the service account for pulling the docker image. 235 | function repo_root() { 236 | local dir="$1" 237 | while [[ ! -d "${dir}/.git" ]]; do 238 | dir="$(dirname "$dir")" 239 | done 240 | echo "${dir}" 241 | } 242 | 243 | # Detect the project root. In CI builds, we assume the script is in 244 | # the git tree and traverse from there, otherwise, traverse from `pwd` 245 | # to find `.git` directory. 246 | if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then 247 | PROGRAM_PATH="$(realpath "$0")" 248 | PROGRAM_DIR="$(dirname "${PROGRAM_PATH}")" 249 | PROJECT_ROOT="$(repo_root "${PROGRAM_DIR}")" 250 | else 251 | PROJECT_ROOT="$(repo_root $(pwd))" 252 | fi 253 | 254 | log_yellow "Changing to the project root: ${PROJECT_ROOT}." 255 | cd "${PROJECT_ROOT}" 256 | 257 | # To support relative path for `TRAMPOLINE_SERVICE_ACCOUNT`, we need 258 | # to use this environment variable in `PROJECT_ROOT`. 259 | if [[ -n "${TRAMPOLINE_SERVICE_ACCOUNT:-}" ]]; then 260 | 261 | mkdir -p "${tmpdir}/gcloud" 262 | gcloud_config_dir="${tmpdir}/gcloud" 263 | 264 | log_yellow "Using isolated gcloud config: ${gcloud_config_dir}." 265 | export CLOUDSDK_CONFIG="${gcloud_config_dir}" 266 | 267 | log_yellow "Using ${TRAMPOLINE_SERVICE_ACCOUNT} for authentication." 268 | gcloud auth activate-service-account \ 269 | --key-file "${TRAMPOLINE_SERVICE_ACCOUNT}" 270 | log_yellow "Configuring Container Registry access" 271 | gcloud auth configure-docker --quiet 272 | fi 273 | 274 | required_envvars=( 275 | # The basic trampoline configurations. 276 | "TRAMPOLINE_IMAGE" 277 | "TRAMPOLINE_BUILD_FILE" 278 | ) 279 | 280 | if [[ -f "${PROJECT_ROOT}/.trampolinerc" ]]; then 281 | source "${PROJECT_ROOT}/.trampolinerc" 282 | fi 283 | 284 | log_yellow "Checking environment variables." 285 | for e in "${required_envvars[@]}" 286 | do 287 | if [[ -z "${!e:-}" ]]; then 288 | log "Missing ${e} env var. Aborting." 289 | exit 1 290 | fi 291 | done 292 | 293 | # We want to support legacy style TRAMPOLINE_BUILD_FILE used with V1 294 | # script: e.g. "github/repo-name/.kokoro/run_tests.sh" 295 | TRAMPOLINE_BUILD_FILE="${TRAMPOLINE_BUILD_FILE#github/*/}" 296 | log_yellow "Using TRAMPOLINE_BUILD_FILE: ${TRAMPOLINE_BUILD_FILE}" 297 | 298 | # ignore error on docker operations and test execution 299 | set +e 300 | 301 | log_yellow "Preparing Docker image." 302 | # We only download the docker image in CI builds. 303 | if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then 304 | # Download the docker image specified by `TRAMPOLINE_IMAGE` 305 | 306 | # We may want to add --max-concurrent-downloads flag. 307 | 308 | log_yellow "Start pulling the Docker image: ${TRAMPOLINE_IMAGE}." 309 | if docker pull "${TRAMPOLINE_IMAGE}"; then 310 | log_green "Finished pulling the Docker image: ${TRAMPOLINE_IMAGE}." 311 | has_image="true" 312 | else 313 | log_red "Failed pulling the Docker image: ${TRAMPOLINE_IMAGE}." 314 | has_image="false" 315 | fi 316 | else 317 | # For local run, check if we have the image. 318 | if docker images "${TRAMPOLINE_IMAGE}" | grep "${TRAMPOLINE_IMAGE%:*}"; then 319 | has_image="true" 320 | else 321 | has_image="false" 322 | fi 323 | fi 324 | 325 | 326 | # The default user for a Docker container has uid 0 (root). To avoid 327 | # creating root-owned files in the build directory we tell docker to 328 | # use the current user ID. 329 | user_uid="$(id -u)" 330 | user_gid="$(id -g)" 331 | user_name="$(id -un)" 332 | 333 | # To allow docker in docker, we add the user to the docker group in 334 | # the host os. 335 | docker_gid=$(cut -d: -f3 < <(getent group docker)) 336 | 337 | update_cache="false" 338 | if [[ "${TRAMPOLINE_DOCKERFILE:-none}" != "none" ]]; then 339 | # Build the Docker image from the source. 340 | context_dir=$(dirname "${TRAMPOLINE_DOCKERFILE}") 341 | docker_build_flags=( 342 | "-f" "${TRAMPOLINE_DOCKERFILE}" 343 | "-t" "${TRAMPOLINE_IMAGE}" 344 | "--build-arg" "UID=${user_uid}" 345 | "--build-arg" "USERNAME=${user_name}" 346 | ) 347 | if [[ "${has_image}" == "true" ]]; then 348 | docker_build_flags+=("--cache-from" "${TRAMPOLINE_IMAGE}") 349 | fi 350 | 351 | log_yellow "Start building the docker image." 352 | if [[ "${TRAMPOLINE_VERBOSE:-false}" == "true" ]]; then 353 | echo "docker build" "${docker_build_flags[@]}" "${context_dir}" 354 | fi 355 | 356 | # ON CI systems, we want to suppress docker build logs, only 357 | # output the logs when it fails. 358 | if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then 359 | if docker build "${docker_build_flags[@]}" "${context_dir}" \ 360 | > "${tmpdir}/docker_build.log" 2>&1; then 361 | if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then 362 | cat "${tmpdir}/docker_build.log" 363 | fi 364 | 365 | log_green "Finished building the docker image." 366 | update_cache="true" 367 | else 368 | log_red "Failed to build the Docker image, aborting." 369 | log_yellow "Dumping the build logs:" 370 | cat "${tmpdir}/docker_build.log" 371 | exit 1 372 | fi 373 | else 374 | if docker build "${docker_build_flags[@]}" "${context_dir}"; then 375 | log_green "Finished building the docker image." 376 | update_cache="true" 377 | else 378 | log_red "Failed to build the Docker image, aborting." 379 | exit 1 380 | fi 381 | fi 382 | else 383 | if [[ "${has_image}" != "true" ]]; then 384 | log_red "We do not have ${TRAMPOLINE_IMAGE} locally, aborting." 385 | exit 1 386 | fi 387 | fi 388 | 389 | # We use an array for the flags so they are easier to document. 390 | docker_flags=( 391 | # Remove the container after it exists. 392 | "--rm" 393 | 394 | # Use the host network. 395 | "--network=host" 396 | 397 | # Run in priviledged mode. We are not using docker for sandboxing or 398 | # isolation, just for packaging our dev tools. 399 | "--privileged" 400 | 401 | # Run the docker script with the user id. Because the docker image gets to 402 | # write in ${PWD} you typically want this to be your user id. 403 | # To allow docker in docker, we need to use docker gid on the host. 404 | "--user" "${user_uid}:${docker_gid}" 405 | 406 | # Pass down the USER. 407 | "--env" "USER=${user_name}" 408 | 409 | # Mount the project directory inside the Docker container. 410 | "--volume" "${PROJECT_ROOT}:${TRAMPOLINE_WORKSPACE}" 411 | "--workdir" "${TRAMPOLINE_WORKSPACE}" 412 | "--env" "PROJECT_ROOT=${TRAMPOLINE_WORKSPACE}" 413 | 414 | # Mount the temporary home directory. 415 | "--volume" "${tmphome}:/h" 416 | "--env" "HOME=/h" 417 | 418 | # Allow docker in docker. 419 | "--volume" "/var/run/docker.sock:/var/run/docker.sock" 420 | 421 | # Mount the /tmp so that docker in docker can mount the files 422 | # there correctly. 423 | "--volume" "/tmp:/tmp" 424 | # Pass down the KOKORO_GFILE_DIR and KOKORO_KEYSTORE_DIR 425 | # TODO(tmatsuo): This part is not portable. 426 | "--env" "TRAMPOLINE_SECRET_DIR=/secrets" 427 | "--volume" "${KOKORO_GFILE_DIR:-/dev/shm}:/secrets/gfile" 428 | "--env" "KOKORO_GFILE_DIR=/secrets/gfile" 429 | "--volume" "${KOKORO_KEYSTORE_DIR:-/dev/shm}:/secrets/keystore" 430 | "--env" "KOKORO_KEYSTORE_DIR=/secrets/keystore" 431 | ) 432 | 433 | # Add an option for nicer output if the build gets a tty. 434 | if [[ -t 0 ]]; then 435 | docker_flags+=("-it") 436 | fi 437 | 438 | # Passing down env vars 439 | for e in "${pass_down_envvars[@]}" 440 | do 441 | if [[ -n "${!e:-}" ]]; then 442 | docker_flags+=("--env" "${e}=${!e}") 443 | fi 444 | done 445 | 446 | # If arguments are given, all arguments will become the commands run 447 | # in the container, otherwise run TRAMPOLINE_BUILD_FILE. 448 | if [[ $# -ge 1 ]]; then 449 | log_yellow "Running the given commands '" "${@:1}" "' in the container." 450 | readonly commands=("${@:1}") 451 | if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then 452 | echo docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" "${commands[@]}" 453 | fi 454 | docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" "${commands[@]}" 455 | else 456 | log_yellow "Running the tests in a Docker container." 457 | docker_flags+=("--entrypoint=${TRAMPOLINE_BUILD_FILE}") 458 | if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then 459 | echo docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" 460 | fi 461 | docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" 462 | fi 463 | 464 | 465 | test_retval=$? 466 | 467 | if [[ ${test_retval} -eq 0 ]]; then 468 | log_green "Build finished with ${test_retval}" 469 | else 470 | log_red "Build finished with ${test_retval}" 471 | fi 472 | 473 | # Only upload it when the test passes. 474 | if [[ "${update_cache}" == "true" ]] && \ 475 | [[ $test_retval == 0 ]] && \ 476 | [[ "${TRAMPOLINE_IMAGE_UPLOAD:-false}" == "true" ]]; then 477 | log_yellow "Uploading the Docker image." 478 | if docker push "${TRAMPOLINE_IMAGE}"; then 479 | log_green "Finished uploading the Docker image." 480 | else 481 | log_red "Failed uploading the Docker image." 482 | fi 483 | # Call trampoline_after_upload_hook if it's defined. 484 | if function_exists trampoline_after_upload_hook; then 485 | trampoline_after_upload_hook 486 | fi 487 | 488 | fi 489 | 490 | exit "${test_retval}" 491 | -------------------------------------------------------------------------------- /.mocharc.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | const config = { 15 | "enable-source-maps": true, 16 | "throw-deprecation": true, 17 | "timeout": 10000, 18 | "recursive": true 19 | } 20 | if (process.env.MOCHA_THROW_DEPRECATION === 'false') { 21 | delete config['throw-deprecation']; 22 | } 23 | if (process.env.MOCHA_REPORTER) { 24 | config.reporter = process.env.MOCHA_REPORTER; 25 | } 26 | if (process.env.MOCHA_REPORTER_OUTPUT) { 27 | config['reporter-option'] = `output=${process.env.MOCHA_REPORTER_OUTPUT}`; 28 | } 29 | module.exports = config 30 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "report-dir": "./.coverage", 3 | "reporter": ["text", "lcov"], 4 | "exclude": [ 5 | "**/*-test", 6 | "**/.coverage", 7 | "**/apis", 8 | "**/benchmark", 9 | "**/conformance", 10 | "**/docs", 11 | "**/samples", 12 | "**/scripts", 13 | "**/protos", 14 | "**/test", 15 | "**/*.d.ts", 16 | ".jsdoc.js", 17 | "**/.jsdoc.js", 18 | "karma.conf.js", 19 | "webpack-tests.config.js", 20 | "webpack.config.js" 21 | ], 22 | "exclude-after-remap": false, 23 | "all": true 24 | } 25 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/coverage 3 | test/fixtures 4 | build/ 5 | docs/ 6 | protos/ 7 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | module.exports = { 16 | ...require('gts/.prettierrc.json') 17 | } 18 | -------------------------------------------------------------------------------- /.readme-partials.yml: -------------------------------------------------------------------------------- 1 | introduction: |- 2 | This module provides an easy to use, higher-level layer for working with [Cloud Logging](https://cloud.google.com/logging/docs), 3 | compatible with [Bunyan](https://www.npmjs.com/package/bunyan). Simply attach this as a transport to your existing Bunyan loggers. 4 | body: |- 5 | ### Using as an express middleware 6 | 7 | ***NOTE: this feature is experimental. The API may change in a backwards 8 | incompatible way until this is deemed stable. Please provide us feedback so 9 | that we can better refine this express integration.*** 10 | 11 | We provide a middleware that can be used in an express application. Apart from 12 | being easy to use, this enables some more powerful features of Cloud 13 | Logging: request bundling. Any application logs emitted on behalf of a specific 14 | request will be shown nested inside the request log as you see in this 15 | screenshot: 16 | 17 | ![Request Bundling Example](https://raw.githubusercontent.com/googleapis/nodejs-logging-bunyan/master/doc/images/request-bundling.png) 18 | 19 | The middleware adds a `bunyan`-style log function to the `request` object. You 20 | can use this wherever you have access to the `request` object (`req` in the 21 | sample below). All log entries that are made on behalf of a specific request are 22 | shown bundled together in the Cloud Logging UI. 23 | 24 | ```javascript 25 | const lb = require('@google-cloud/logging-bunyan'); 26 | 27 | // Import express module and create an http server. 28 | const express = require('express'); 29 | 30 | async function startServer() { 31 | const {logger, mw} = await lb.express.middleware(); 32 | const app = express(); 33 | 34 | // Install the logging middleware. This ensures that a Bunyan-style `log` 35 | // function is available on the `request` object. Attach this as one of the 36 | // earliest middleware to make sure that log function is available in all the 37 | // subsequent middleware and routes. 38 | app.use(mw); 39 | 40 | // Setup an http route and a route handler. 41 | app.get('/', (req, res) => { 42 | // `req.log` can be used as a bunyan style log method. All logs generated 43 | // using `req.log` use the current request context. That is, all logs 44 | // corresponding to a specific request will be bundled in the Cloud UI. 45 | req.log.info('this is an info log message'); 46 | res.send('hello world'); 47 | }); 48 | 49 | // `logger` can be used as a global logger, one not correlated to any specific 50 | // request. 51 | logger.info({port: 8080}, 'bonjour'); 52 | 53 | // Start listening on the http server. 54 | app.listen(8080, () => { 55 | console.log('http server listening on port 8080'); 56 | }); 57 | } 58 | 59 | startServer(); 60 | ``` 61 | 62 | ### Error Reporting 63 | 64 | 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: 65 | 66 | ```javascript 67 | const loggingBunyan = new LoggingBunyan({ 68 | serviceContext: { 69 | service: 'my-service', // required to report logged errors 70 | // to the Google Cloud Error Reporting 71 | // console 72 | version: 'my-version' 73 | } 74 | }); 75 | ``` 76 | 77 | It is an error to specify a `serviceContext` but not specify `serviceContext.service`. 78 | 79 | 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. 80 | 81 | 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. 82 | 83 | ### Special Payload Fields in LogEntry 84 | 85 | 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. 86 | 87 | ### LogEntry Labels 88 | 89 | If the bunyan log record contains a label property where all the values are strings, we automatically promote that 90 | property to be the [`LogEntry.labels`](https://cloud.google.com/logging/docs/reference/v2/rpc/google.logging.v2#logentry) value rather 91 | than being one of the properties in the `payload` fields. This makes it easier to filter the logs in the UI using the labels. 92 | 93 | ```javascript 94 | logger.info({labels: {someKey: 'some value'}}, 'test log message'); 95 | ``` 96 | 97 | All the label values must be strings for this automatic promotion to work. Otherwise the labels are left in the payload. 98 | 99 | ### Formatting Request Logs 100 | 101 | 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: 102 | 103 | ![Request Log Example](https://raw.githubusercontent.com/googleapis/nodejs-logging-bunyan/master/doc/images/request-log.png) 104 | 105 | ```js 106 | logger.info({ 107 | httpRequest: { 108 | status: res.statusCode, 109 | requestUrl: req.url, 110 | requestMethod: req.method, 111 | remoteIp: req.connection.remoteAddress, 112 | // etc. 113 | } 114 | }, req.path); 115 | ``` 116 | 117 | 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`.) 118 | 119 | ### Correlating Logs with Traces 120 | 121 | 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: 122 | 123 | ![Logs in Trace Example](https://raw.githubusercontent.com/googleapis/nodejs-logging-bunyan/master/doc/images/bunyan-logs-in-trace.png) 124 | 125 | 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: 126 | 127 | ```js 128 | const bunyan = require('bunyan'); 129 | // Node 6+ 130 | const {LoggingBunyan, LOGGING_TRACE_KEY} = require('@google-cloud/logging-bunyan'); 131 | const loggingBunyan = LoggingBunyan(); 132 | 133 | ... 134 | 135 | logger.info({ 136 | [LOGGING_TRACE_KEY]: 'custom-trace-value' 137 | }, 'Bunyan log entry with custom trace field'); 138 | ``` 139 | 140 | ### Error handling with a default callback 141 | The `LoggingBunyan` class creates an instance of `Logging` which creates the `Log` class from `@google-cloud/logging` package to write log entries. 142 | 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. 143 | If the error is not handled, it could crash the application. One possible way to handle the error is to provide a default callback 144 | to the `LoggingBunyan` constructor which will be used to initialize the `Log` object with that callback like in the example below: 145 | 146 | ```js 147 | // Imports the Google Cloud client library for Bunyan 148 | const {LoggingBunyan} = require('@google-cloud/logging-bunyan'); 149 | // Creates a client 150 | const loggingBunyan = new LoggingBunyan({ 151 | projectId: 'your-project-id', 152 | keyFilename: '/path/to/key.json', 153 | defaultCallback: err => { 154 | if (err) { 155 | console.log('Error occured: ' + err); 156 | } 157 | }, 158 | }); 159 | ``` 160 | 161 | ### Alternative way to ingest logs in Google Cloud managed environments 162 | If you use this library with the Cloud Logging Agent, you can configure the handler to output logs to `process.stdout` using 163 | the [structured logging Json format](https://cloud.google.com/logging/docs/structured-logging#special-payload-fields). 164 | To do this, add `redirectToStdout: true` parameter to the `LoggingBunyan` constructor as in sample below. 165 | You can use this parameter when running applications in Google Cloud managed environments such as AppEngine, Cloud Run, 166 | Cloud Function or GKE. The logger agent installed on these environments can capture `process.stdout` and ingest it into Cloud Logging. 167 | The agent can parse structured logs printed to `process.stdout` and capture additional log metadata beside the log payload. 168 | It is recommended to set `redirectToStdout: true` in serverless environments like Cloud Functions since it could 169 | decrease logging record loss upon execution termination - since all logs are written to `process.stdout` those 170 | would be picked up by the Cloud Logging Agent running in Google Cloud managed environment. 171 | Note that there is also a `useMessageField` option which controls if "message" field is used to store 172 | structured, non-text data inside `jsonPayload` field when `redirectToStdout` is set. By default `useMessageField` is always `true`. 173 | Set the `skipParentEntryForCloudRun` option to skip creating an entry for the request itself as Cloud Run already automatically creates 174 | such log entries. This might become the default behaviour in a next major version. 175 | 176 | ```js 177 | // Imports the Google Cloud client library for Bunyan 178 | const {LoggingBunyan} = require('@google-cloud/logging-bunyan'); 179 | 180 | // Creates a client 181 | const loggingBunyan = new LoggingBunyan({ 182 | projectId: 'your-project-id', 183 | keyFilename: '/path/to/key.json', 184 | redirectToStdout: true, 185 | }); 186 | ``` 187 | -------------------------------------------------------------------------------- /.repo-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "logging-bunyan", 3 | "name_pretty": "Cloud Logging for Bunyan", 4 | "product_documentation": "https://cloud.google.com/logging", 5 | "client_documentation": "https://cloud.google.com/nodejs/docs/reference/logging-bunyan/latest", 6 | "issue_tracker": "https://issuetracker.google.com/savedsearches/559764", 7 | "release_level": "stable", 8 | "language": "nodejs", 9 | "repo": "googleapis/nodejs-logging-bunyan", 10 | "distribution_name": "@google-cloud/logging-bunyan", 11 | "api_id": "logging.googleapis.com", 12 | "codeowner_team": "@googleapis/api-logging", 13 | "library_type": "OTHER" 14 | } 15 | -------------------------------------------------------------------------------- /.trampolinerc: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Template for .trampolinerc 16 | 17 | # Add required env vars here. 18 | required_envvars+=( 19 | ) 20 | 21 | # Add env vars which are passed down into the container here. 22 | pass_down_envvars+=( 23 | "AUTORELEASE_PR" 24 | "VERSION" 25 | ) 26 | 27 | # Prevent unintentional override on the default image. 28 | if [[ "${TRAMPOLINE_IMAGE_UPLOAD:-false}" == "true" ]] && \ 29 | [[ -z "${TRAMPOLINE_IMAGE:-}" ]]; then 30 | echo "Please set TRAMPOLINE_IMAGE if you want to upload the Docker image." 31 | exit 1 32 | fi 33 | 34 | # Define the default value if it makes sense. 35 | if [[ -z "${TRAMPOLINE_IMAGE_UPLOAD:-}" ]]; then 36 | TRAMPOLINE_IMAGE_UPLOAD="" 37 | fi 38 | 39 | if [[ -z "${TRAMPOLINE_IMAGE:-}" ]]; then 40 | TRAMPOLINE_IMAGE="" 41 | fi 42 | 43 | if [[ -z "${TRAMPOLINE_DOCKERFILE:-}" ]]; then 44 | TRAMPOLINE_DOCKERFILE="" 45 | fi 46 | 47 | if [[ -z "${TRAMPOLINE_BUILD_FILE:-}" ]]; then 48 | TRAMPOLINE_BUILD_FILE="" 49 | fi 50 | 51 | # Secret Manager secrets. 52 | source ${PROJECT_ROOT}/.kokoro/populate-secrets.sh 53 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | In the interest of fostering an open and welcoming environment, we as 7 | contributors and maintainers pledge to making participation in our project and 8 | our community a harassment-free experience for everyone, regardless of age, body 9 | size, disability, ethnicity, gender identity and expression, level of 10 | experience, education, socio-economic status, nationality, personal appearance, 11 | race, religion, or sexual identity and orientation. 12 | 13 | ## Our Standards 14 | 15 | Examples of behavior that contributes to creating a positive environment 16 | include: 17 | 18 | * Using welcoming and inclusive language 19 | * Being respectful of differing viewpoints and experiences 20 | * Gracefully accepting constructive criticism 21 | * Focusing on what is best for the community 22 | * Showing empathy towards other community members 23 | 24 | Examples of unacceptable behavior by participants include: 25 | 26 | * The use of sexualized language or imagery and unwelcome sexual attention or 27 | advances 28 | * Trolling, insulting/derogatory comments, and personal or political attacks 29 | * Public or private harassment 30 | * Publishing others' private information, such as a physical or electronic 31 | address, without explicit permission 32 | * Other conduct which could reasonably be considered inappropriate in a 33 | professional setting 34 | 35 | ## Our Responsibilities 36 | 37 | Project maintainers are responsible for clarifying the standards of acceptable 38 | behavior and are expected to take appropriate and fair corrective action in 39 | response to any instances of unacceptable behavior. 40 | 41 | Project maintainers have the right and responsibility to remove, edit, or reject 42 | comments, commits, code, wiki edits, issues, and other contributions that are 43 | not aligned to this Code of Conduct, or to ban temporarily or permanently any 44 | contributor for other behaviors that they deem inappropriate, threatening, 45 | offensive, or harmful. 46 | 47 | ## Scope 48 | 49 | This Code of Conduct applies both within project spaces and in public spaces 50 | when an individual is representing the project or its community. Examples of 51 | representing a project or community include using an official project e-mail 52 | address, posting via an official social media account, or acting as an appointed 53 | representative at an online or offline event. Representation of a project may be 54 | further defined and clarified by project maintainers. 55 | 56 | This Code of Conduct also applies outside the project spaces when the Project 57 | Steward has a reasonable belief that an individual's behavior may have a 58 | negative impact on the project or its community. 59 | 60 | ## Conflict Resolution 61 | 62 | We do not believe that all conflict is bad; healthy debate and disagreement 63 | often yield positive results. However, it is never okay to be disrespectful or 64 | to engage in behavior that violates the project’s code of conduct. 65 | 66 | If you see someone violating the code of conduct, you are encouraged to address 67 | the behavior directly with those involved. Many issues can be resolved quickly 68 | and easily, and this gives people more control over the outcome of their 69 | dispute. If you are unable to resolve the matter for any reason, or if the 70 | behavior is threatening or harassing, report it. We are dedicated to providing 71 | an environment where participants feel welcome and safe. 72 | 73 | Reports should be directed to *googleapis-stewards@google.com*, the 74 | Project Steward(s) for *Google Cloud Client Libraries*. It is the Project Steward’s duty to 75 | receive and address reported violations of the code of conduct. They will then 76 | work with a committee consisting of representatives from the Open Source 77 | Programs Office and the Google Open Source Strategy team. If for any reason you 78 | are uncomfortable reaching out to the Project Steward, please email 79 | opensource@google.com. 80 | 81 | We will investigate every complaint, but you may not receive a direct response. 82 | We will use our discretion in determining when and how to follow up on reported 83 | incidents, which may range from not taking action to permanent expulsion from 84 | the project and project-sponsored spaces. We will notify the accused of the 85 | report and provide them an opportunity to discuss it before any action is taken. 86 | The identity of the reporter will be omitted from the details of the report 87 | supplied to the accused. In potentially harmful situations, such as ongoing 88 | harassment or threats to anyone's safety, we may take action without notice. 89 | 90 | ## Attribution 91 | 92 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, 93 | available at 94 | https://www.contributor-covenant.org/version/1/4/code-of-conduct.html -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to become a contributor and submit your own code 2 | 3 | **Table of contents** 4 | 5 | * [Contributor License Agreements](#contributor-license-agreements) 6 | * [Contributing a patch](#contributing-a-patch) 7 | * [Running the tests](#running-the-tests) 8 | * [Releasing the library](#releasing-the-library) 9 | 10 | ## Contributor License Agreements 11 | 12 | We'd love to accept your sample apps and patches! Before we can take them, we 13 | have to jump a couple of legal hurdles. 14 | 15 | Please fill out either the individual or corporate Contributor License Agreement 16 | (CLA). 17 | 18 | * If you are an individual writing original source code and you're sure you 19 | own the intellectual property, then you'll need to sign an [individual CLA](https://developers.google.com/open-source/cla/individual). 20 | * If you work for a company that wants to allow you to contribute your work, 21 | then you'll need to sign a [corporate CLA](https://developers.google.com/open-source/cla/corporate). 22 | 23 | Follow either of the two links above to access the appropriate CLA and 24 | instructions for how to sign and return it. Once we receive it, we'll be able to 25 | accept your pull requests. 26 | 27 | ## Contributing A Patch 28 | 29 | 1. Submit an issue describing your proposed change to the repo in question. 30 | 1. The repo owner will respond to your issue promptly. 31 | 1. If your proposed change is accepted, and you haven't already done so, sign a 32 | Contributor License Agreement (see details above). 33 | 1. Fork the desired repo, develop and test your code changes. 34 | 1. Ensure that your code adheres to the existing style in the code to which 35 | you are contributing. 36 | 1. Ensure that your code has an appropriate set of tests which all pass. 37 | 1. Title your pull request following [Conventional Commits](https://www.conventionalcommits.org/) styling. 38 | 1. Submit a pull request. 39 | 40 | ### Before you begin 41 | 42 | 1. [Select or create a Cloud Platform project][projects]. 43 | 1. [Enable the Cloud Logging for Bunyan API][enable_api]. 44 | 1. [Set up authentication with a service account][auth] so you can access the 45 | API from your local workstation. 46 | 47 | 48 | ## Running the tests 49 | 50 | 1. [Prepare your environment for Node.js setup][setup]. 51 | 52 | 1. Install dependencies: 53 | 54 | npm install 55 | 56 | 1. Run the tests: 57 | 58 | # Run unit tests. 59 | npm test 60 | 61 | # Run sample integration tests. 62 | npm run samples-test 63 | 64 | # Run all system tests. 65 | npm run system-test 66 | 67 | 1. Lint (and maybe fix) any changes: 68 | 69 | npm run fix 70 | 71 | [setup]: https://cloud.google.com/nodejs/docs/setup 72 | [projects]: https://console.cloud.google.com/project 73 | [billing]: https://support.google.com/cloud/answer/6293499#enable-billing 74 | [enable_api]: https://console.cloud.google.com/flows/enableapi?apiid=logging.googleapis.com 75 | [auth]: https://cloud.google.com/docs/authentication/getting-started -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [//]: # "This README.md file is auto-generated, all changes to this file will be lost." 2 | [//]: # "To regenerate it, use `python -m synthtool`." 3 | Google Cloud Platform logo 4 | 5 | # [Cloud Logging for Bunyan: Node.js Client](https://github.com/googleapis/nodejs-logging-bunyan) 6 | 7 | [![release level](https://img.shields.io/badge/release%20level-stable-brightgreen.svg?style=flat)](https://cloud.google.com/terms/launch-stages) 8 | [![npm version](https://img.shields.io/npm/v/@google-cloud/logging-bunyan.svg)](https://www.npmjs.org/package/@google-cloud/logging-bunyan) 9 | 10 | 11 | 12 | 13 | This module provides an easy to use, higher-level layer for working with [Cloud Logging](https://cloud.google.com/logging/docs), 14 | compatible with [Bunyan](https://www.npmjs.com/package/bunyan). Simply attach this as a transport to your existing Bunyan loggers. 15 | 16 | 17 | A comprehensive list of changes in each version may be found in 18 | [the CHANGELOG](https://github.com/googleapis/nodejs-logging-bunyan/blob/main/CHANGELOG.md). 19 | 20 | * [Cloud Logging for Bunyan Node.js Client API Reference][client-docs] 21 | * [Cloud Logging for Bunyan Documentation][product-docs] 22 | * [github.com/googleapis/nodejs-logging-bunyan](https://github.com/googleapis/nodejs-logging-bunyan) 23 | 24 | Read more about the client libraries for Cloud APIs, including the older 25 | Google APIs Client Libraries, in [Client Libraries Explained][explained]. 26 | 27 | [explained]: https://cloud.google.com/apis/docs/client-libraries-explained 28 | 29 | **Table of contents:** 30 | 31 | 32 | * [Quickstart](#quickstart) 33 | * [Before you begin](#before-you-begin) 34 | * [Installing the client library](#installing-the-client-library) 35 | * [Using the client library](#using-the-client-library) 36 | * [Samples](#samples) 37 | * [Versioning](#versioning) 38 | * [Contributing](#contributing) 39 | * [License](#license) 40 | 41 | ## Quickstart 42 | 43 | ### Before you begin 44 | 45 | 1. [Select or create a Cloud Platform project][projects]. 46 | 1. [Enable the Cloud Logging for Bunyan API][enable_api]. 47 | 1. [Set up authentication][auth] so you can access the 48 | API from your local workstation. 49 | 50 | ### Installing the client library 51 | 52 | ```bash 53 | npm install @google-cloud/logging-bunyan 54 | ``` 55 | 56 | 57 | ### Using the client library 58 | 59 | ```javascript 60 | const bunyan = require('bunyan'); 61 | 62 | // Imports the Google Cloud client library for Bunyan 63 | const {LoggingBunyan} = require('@google-cloud/logging-bunyan'); 64 | 65 | // Creates a Bunyan Cloud Logging client 66 | const loggingBunyan = new LoggingBunyan(); 67 | 68 | // Create a Bunyan logger that streams to Cloud Logging 69 | // Logs will be written to: "projects/YOUR_PROJECT_ID/logs/bunyan_log" 70 | const logger = bunyan.createLogger({ 71 | // The JSON payload of the log as it appears in Cloud Logging 72 | // will contain "name": "my-service" 73 | name: 'my-service', 74 | streams: [ 75 | // Log to the console at 'info' and above 76 | {stream: process.stdout, level: 'info'}, 77 | // And log to Cloud Logging, logging at 'info' and above 78 | loggingBunyan.stream('info'), 79 | ], 80 | }); 81 | 82 | // Writes some log entries 83 | logger.error('warp nacelles offline'); 84 | logger.info('shields at 99%'); 85 | 86 | ``` 87 | ### Using as an express middleware 88 | 89 | ***NOTE: this feature is experimental. The API may change in a backwards 90 | incompatible way until this is deemed stable. Please provide us feedback so 91 | that we can better refine this express integration.*** 92 | 93 | We provide a middleware that can be used in an express application. Apart from 94 | being easy to use, this enables some more powerful features of Cloud 95 | Logging: request bundling. Any application logs emitted on behalf of a specific 96 | request will be shown nested inside the request log as you see in this 97 | screenshot: 98 | 99 | ![Request Bundling Example](https://raw.githubusercontent.com/googleapis/nodejs-logging-bunyan/master/doc/images/request-bundling.png) 100 | 101 | The middleware adds a `bunyan`-style log function to the `request` object. You 102 | can use this wherever you have access to the `request` object (`req` in the 103 | sample below). All log entries that are made on behalf of a specific request are 104 | shown bundled together in the Cloud Logging UI. 105 | 106 | ```javascript 107 | const lb = require('@google-cloud/logging-bunyan'); 108 | 109 | // Import express module and create an http server. 110 | const express = require('express'); 111 | 112 | async function startServer() { 113 | const {logger, mw} = await lb.express.middleware(); 114 | const app = express(); 115 | 116 | // Install the logging middleware. This ensures that a Bunyan-style `log` 117 | // function is available on the `request` object. Attach this as one of the 118 | // earliest middleware to make sure that log function is available in all the 119 | // subsequent middleware and routes. 120 | app.use(mw); 121 | 122 | // Setup an http route and a route handler. 123 | app.get('/', (req, res) => { 124 | // `req.log` can be used as a bunyan style log method. All logs generated 125 | // using `req.log` use the current request context. That is, all logs 126 | // corresponding to a specific request will be bundled in the Cloud UI. 127 | req.log.info('this is an info log message'); 128 | res.send('hello world'); 129 | }); 130 | 131 | // `logger` can be used as a global logger, one not correlated to any specific 132 | // request. 133 | logger.info({port: 8080}, 'bonjour'); 134 | 135 | // Start listening on the http server. 136 | app.listen(8080, () => { 137 | console.log('http server listening on port 8080'); 138 | }); 139 | } 140 | 141 | startServer(); 142 | ``` 143 | 144 | ### Error Reporting 145 | 146 | 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: 147 | 148 | ```javascript 149 | const loggingBunyan = new LoggingBunyan({ 150 | serviceContext: { 151 | service: 'my-service', // required to report logged errors 152 | // to the Google Cloud Error Reporting 153 | // console 154 | version: 'my-version' 155 | } 156 | }); 157 | ``` 158 | 159 | It is an error to specify a `serviceContext` but not specify `serviceContext.service`. 160 | 161 | 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. 162 | 163 | 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. 164 | 165 | ### Special Payload Fields in LogEntry 166 | 167 | 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. 168 | 169 | ### LogEntry Labels 170 | 171 | If the bunyan log record contains a label property where all the values are strings, we automatically promote that 172 | property to be the [`LogEntry.labels`](https://cloud.google.com/logging/docs/reference/v2/rpc/google.logging.v2#logentry) value rather 173 | than being one of the properties in the `payload` fields. This makes it easier to filter the logs in the UI using the labels. 174 | 175 | ```javascript 176 | logger.info({labels: {someKey: 'some value'}}, 'test log message'); 177 | ``` 178 | 179 | All the label values must be strings for this automatic promotion to work. Otherwise the labels are left in the payload. 180 | 181 | ### Formatting Request Logs 182 | 183 | 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: 184 | 185 | ![Request Log Example](https://raw.githubusercontent.com/googleapis/nodejs-logging-bunyan/master/doc/images/request-log.png) 186 | 187 | ```js 188 | logger.info({ 189 | httpRequest: { 190 | status: res.statusCode, 191 | requestUrl: req.url, 192 | requestMethod: req.method, 193 | remoteIp: req.connection.remoteAddress, 194 | // etc. 195 | } 196 | }, req.path); 197 | ``` 198 | 199 | 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`.) 200 | 201 | ### Correlating Logs with Traces 202 | 203 | 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: 204 | 205 | ![Logs in Trace Example](https://raw.githubusercontent.com/googleapis/nodejs-logging-bunyan/master/doc/images/bunyan-logs-in-trace.png) 206 | 207 | 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: 208 | 209 | ```js 210 | const bunyan = require('bunyan'); 211 | // Node 6+ 212 | const {LoggingBunyan, LOGGING_TRACE_KEY} = require('@google-cloud/logging-bunyan'); 213 | const loggingBunyan = LoggingBunyan(); 214 | 215 | ... 216 | 217 | logger.info({ 218 | [LOGGING_TRACE_KEY]: 'custom-trace-value' 219 | }, 'Bunyan log entry with custom trace field'); 220 | ``` 221 | 222 | ### Error handling with a default callback 223 | The `LoggingBunyan` class creates an instance of `Logging` which creates the `Log` class from `@google-cloud/logging` package to write log entries. 224 | 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. 225 | If the error is not handled, it could crash the application. One possible way to handle the error is to provide a default callback 226 | to the `LoggingBunyan` constructor which will be used to initialize the `Log` object with that callback like in the example below: 227 | 228 | ```js 229 | // Imports the Google Cloud client library for Bunyan 230 | const {LoggingBunyan} = require('@google-cloud/logging-bunyan'); 231 | // Creates a client 232 | const loggingBunyan = new LoggingBunyan({ 233 | projectId: 'your-project-id', 234 | keyFilename: '/path/to/key.json', 235 | defaultCallback: err => { 236 | if (err) { 237 | console.log('Error occured: ' + err); 238 | } 239 | }, 240 | }); 241 | ``` 242 | 243 | ### Alternative way to ingest logs in Google Cloud managed environments 244 | If you use this library with the Cloud Logging Agent, you can configure the handler to output logs to `process.stdout` using 245 | the [structured logging Json format](https://cloud.google.com/logging/docs/structured-logging#special-payload-fields). 246 | To do this, add `redirectToStdout: true` parameter to the `LoggingBunyan` constructor as in sample below. 247 | You can use this parameter when running applications in Google Cloud managed environments such as AppEngine, Cloud Run, 248 | Cloud Function or GKE. The logger agent installed on these environments can capture `process.stdout` and ingest it into Cloud Logging. 249 | The agent can parse structured logs printed to `process.stdout` and capture additional log metadata beside the log payload. 250 | It is recommended to set `redirectToStdout: true` in serverless environments like Cloud Functions since it could 251 | decrease logging record loss upon execution termination - since all logs are written to `process.stdout` those 252 | would be picked up by the Cloud Logging Agent running in Google Cloud managed environment. 253 | Note that there is also a `useMessageField` option which controls if "message" field is used to store 254 | structured, non-text data inside `jsonPayload` field when `redirectToStdout` is set. By default `useMessageField` is always `true`. 255 | Set the `skipParentEntryForCloudRun` option to skip creating an entry for the request itself as Cloud Run already automatically creates 256 | such log entries. This might become the default behaviour in a next major version. 257 | 258 | ```js 259 | // Imports the Google Cloud client library for Bunyan 260 | const {LoggingBunyan} = require('@google-cloud/logging-bunyan'); 261 | 262 | // Creates a client 263 | const loggingBunyan = new LoggingBunyan({ 264 | projectId: 'your-project-id', 265 | keyFilename: '/path/to/key.json', 266 | redirectToStdout: true, 267 | }); 268 | ``` 269 | 270 | 271 | ## Samples 272 | 273 | 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. 274 | 275 | | Sample | Source Code | Try it | 276 | | --------------------------- | --------------------------------- | ------ | 277 | | 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) | 278 | | 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) | 279 | | 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) | 280 | 281 | 282 | 283 | The [Cloud Logging for Bunyan Node.js Client API Reference][client-docs] documentation 284 | also contains samples. 285 | 286 | ## Supported Node.js Versions 287 | 288 | Our client libraries follow the [Node.js release schedule](https://github.com/nodejs/release#release-schedule). 289 | Libraries are compatible with all current _active_ and _maintenance_ versions of 290 | Node.js. 291 | If you are using an end-of-life version of Node.js, we recommend that you update 292 | as soon as possible to an actively supported LTS version. 293 | 294 | Google's client libraries support legacy versions of Node.js runtimes on a 295 | best-efforts basis with the following warnings: 296 | 297 | * Legacy versions are not tested in continuous integration. 298 | * Some security patches and features cannot be backported. 299 | * Dependencies cannot be kept up-to-date. 300 | 301 | Client libraries targeting some end-of-life versions of Node.js are available, and 302 | can be installed through npm [dist-tags](https://docs.npmjs.com/cli/dist-tag). 303 | The dist-tags follow the naming convention `legacy-(version)`. 304 | For example, `npm install @google-cloud/logging-bunyan@legacy-8` installs client libraries 305 | for versions compatible with Node.js 8. 306 | 307 | ## Versioning 308 | 309 | This library follows [Semantic Versioning](http://semver.org/). 310 | 311 | 312 | 313 | This library is considered to be **stable**. The code surface will not change in backwards-incompatible ways 314 | unless absolutely necessary (e.g. because of critical security issues) or with 315 | an extensive deprecation period. Issues and requests against **stable** libraries 316 | are addressed with the highest priority. 317 | 318 | 319 | 320 | 321 | 322 | 323 | More Information: [Google Cloud Platform Launch Stages][launch_stages] 324 | 325 | [launch_stages]: https://cloud.google.com/terms/launch-stages 326 | 327 | ## Contributing 328 | 329 | Contributions welcome! See the [Contributing Guide](https://github.com/googleapis/nodejs-logging-bunyan/blob/main/CONTRIBUTING.md). 330 | 331 | Please note that this `README.md`, the `samples/README.md`, 332 | and a variety of configuration files in this repository (including `.nycrc` and `tsconfig.json`) 333 | are generated from a central template. To edit one of these files, make an edit 334 | to its templates in 335 | [directory](https://github.com/googleapis/synthtool). 336 | 337 | ## License 338 | 339 | Apache Version 2.0 340 | 341 | See [LICENSE](https://github.com/googleapis/nodejs-logging-bunyan/blob/main/LICENSE) 342 | 343 | [client-docs]: https://cloud.google.com/nodejs/docs/reference/logging-bunyan/latest 344 | [product-docs]: https://cloud.google.com/logging 345 | [shell_img]: https://gstatic.com/cloudssh/images/open-btn.png 346 | [projects]: https://console.cloud.google.com/project 347 | [billing]: https://support.google.com/cloud/answer/6293499#enable-billing 348 | [enable_api]: https://console.cloud.google.com/flows/enableapi?apiid=logging.googleapis.com 349 | [auth]: https://cloud.google.com/docs/authentication/external/set-up-adc-local 350 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | To report a security issue, please use [g.co/vulnz](https://g.co/vulnz). 4 | 5 | The Google Security Team will respond within 5 working days of your report on g.co/vulnz. 6 | 7 | We use g.co/vulnz for our intake, and do coordination and disclosure here using GitHub Security Advisory to privately discuss and fix the issue. 8 | -------------------------------------------------------------------------------- /doc/images/bunyan-logs-in-trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/nodejs-logging-bunyan/34a278c724f9bfcf1511e22b7deb234007f713bf/doc/images/bunyan-logs-in-trace.png -------------------------------------------------------------------------------- /doc/images/request-bundling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/nodejs-logging-bunyan/34a278c724f9bfcf1511e22b7deb234007f713bf/doc/images/request-bundling.png -------------------------------------------------------------------------------- /doc/images/request-log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleapis/nodejs-logging-bunyan/34a278c724f9bfcf1511e22b7deb234007f713bf/doc/images/request-log.png -------------------------------------------------------------------------------- /linkinator.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "recurse": true, 3 | "skip": [ 4 | "https://codecov.io/gh/googleapis/", 5 | "www.googleapis.com", 6 | "img.shields.io" 7 | ], 8 | "silent": true, 9 | "concurrency": 10 10 | } 11 | -------------------------------------------------------------------------------- /owlbot.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os 16 | import synthtool as s 17 | import synthtool.gcp as gcp 18 | import synthtool.languages.node as node 19 | import logging 20 | 21 | logging.basicConfig(level=logging.DEBUG) 22 | 23 | 24 | common_templates = gcp.CommonTemplates() 25 | templates = common_templates.node_library(source_location='build/src') 26 | s.move(templates, excludes=[ 27 | ".github/auto-label.yaml", 28 | ".github/release-please.yml", 29 | ".github/CODEOWNERS", 30 | ".github/sync-repo-settings.yaml", 31 | ]) 32 | node.fix() 33 | 34 | # -------------------------------------------------------------------------- 35 | # Modify test configs 36 | # -------------------------------------------------------------------------- 37 | 38 | # add shared environment variables to test configs 39 | s.move( 40 | ".kokoro/common_env_vars.cfg", 41 | ".kokoro/common.cfg", 42 | merge=lambda src, dst, _, : f"{dst}\n{src}", 43 | ) 44 | for path, subdirs, files in os.walk(f".kokoro/continuous"): 45 | for name in files: 46 | if name == "common.cfg": 47 | file_path = os.path.join(path, name) 48 | s.move( 49 | ".kokoro/common_env_vars.cfg", 50 | file_path, 51 | merge=lambda src, dst, _, : f"{dst}\n{src}", 52 | ) 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@google-cloud/logging-bunyan", 3 | "description": "Cloud Logging stream for Bunyan", 4 | "version": "5.1.0", 5 | "license": "Apache-2.0", 6 | "author": "Google Inc.", 7 | "engines": { 8 | "node": ">=14.0.0" 9 | }, 10 | "repository": "googleapis/nodejs-logging-bunyan", 11 | "main": "./build/src/index.js", 12 | "types": "./build/src/index.d.ts", 13 | "files": [ 14 | "build/src", 15 | "!build/src/**/*.map" 16 | ], 17 | "keywords": [ 18 | "google apis client", 19 | "google api client", 20 | "google apis", 21 | "google api", 22 | "google", 23 | "google cloud platform", 24 | "google cloud", 25 | "cloud", 26 | "google logging", 27 | "logging", 28 | "stackdriver logging", 29 | "stackdriver", 30 | "bunyan stream", 31 | "winston" 32 | ], 33 | "scripts": { 34 | "predocs": "npm run compile", 35 | "docs": "jsdoc -c .jsdoc.js", 36 | "lint": "gts check", 37 | "presamples-test": "npm run compile", 38 | "presystem-test": "npm run compile", 39 | "samples-test": "cd samples/ && npm link ../ && npm test && cd ../", 40 | "system-test": "mocha build/system-test --timeout 600000", 41 | "test": "c8 mocha --recursive build/test", 42 | "clean": "gts clean", 43 | "compile": "tsc -p .", 44 | "postcompile": "cpy \"src/types/*\" build/src/types", 45 | "fix": "gts fix", 46 | "prepare": "npm run compile", 47 | "pretest": "npm run compile", 48 | "docs-test": "linkinator docs", 49 | "predocs-test": "npm run docs", 50 | "prelint": "cd samples; npm link ../; npm install", 51 | "precompile": "gts clean" 52 | }, 53 | "dependencies": { 54 | "@google-cloud/logging": "^11.0.0", 55 | "google-auth-library": "^9.0.0" 56 | }, 57 | "devDependencies": { 58 | "@google-cloud/common": "^5.0.0", 59 | "@types/bunyan": "^1.8.4", 60 | "@types/express": "^4.16.0", 61 | "@types/mocha": "^9.0.0", 62 | "@types/node": "^20.4.9", 63 | "@types/proxyquire": "^1.3.28", 64 | "@types/uuid": "^10.0.0", 65 | "bunyan": "^1.8.12", 66 | "c8": "^9.0.0", 67 | "codecov": "^3.0.2", 68 | "cpy-cli": "^4.0.0", 69 | "delay": "^5.0.0", 70 | "express": "^4.16.3", 71 | "gts": "^5.0.0", 72 | "jsdoc": "^4.0.0", 73 | "jsdoc-fresh": "^3.0.0", 74 | "jsdoc-region-tag": "^3.0.0", 75 | "linkinator": "^3.0.0", 76 | "mocha": "^9.2.2", 77 | "post-install-check": "0.0.1", 78 | "proxyquire": "^2.0.1", 79 | "typescript": "^5.1.6", 80 | "uuid": "^10.0.0" 81 | }, 82 | "peerDependencies": { 83 | "bunyan": "*" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base", 4 | "docker:disable", 5 | ":disableDependencyDashboard" 6 | ], 7 | "constraintsFiltering": "strict", 8 | "pinVersions": false, 9 | "rebaseStalePrs": true, 10 | "schedule": [ 11 | "after 9am and before 3pm" 12 | ], 13 | "gitAuthor": null, 14 | "packageRules": [ 15 | { 16 | "extends": "packages:linters", 17 | "groupName": "linters" 18 | } 19 | ], 20 | "ignoreDeps": ["typescript"] 21 | } 22 | -------------------------------------------------------------------------------- /samples/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | --- 2 | rules: 3 | no-console: off 4 | -------------------------------------------------------------------------------- /samples/README.md: -------------------------------------------------------------------------------- 1 | [//]: # "This README.md file is auto-generated, all changes to this file will be lost." 2 | [//]: # "To regenerate it, use `python -m synthtool`." 3 | Google Cloud Platform logo 4 | 5 | # [Cloud Logging for Bunyan: Node.js Samples](https://github.com/googleapis/nodejs-logging-bunyan) 6 | 7 | [![Open in Cloud Shell][shell_img]][shell_link] 8 | 9 | This module provides an easy to use, higher-level layer for working with [Cloud Logging](https://cloud.google.com/logging/docs), 10 | compatible with [Bunyan](https://www.npmjs.com/package/bunyan). Simply attach this as a transport to your existing Bunyan loggers. 11 | 12 | ## Table of Contents 13 | 14 | * [Before you begin](#before-you-begin) 15 | * [Samples](#samples) 16 | * [Express](#express) 17 | * [Quickstart](#quickstart) 18 | * [Explict Auth Setup](#explict-auth-setup) 19 | 20 | ## Before you begin 21 | 22 | Before running the samples, make sure you've followed the steps outlined in 23 | [Using the client library](https://github.com/googleapis/nodejs-logging-bunyan#using-the-client-library). 24 | 25 | `cd samples` 26 | 27 | `npm install` 28 | 29 | `cd ..` 30 | 31 | ## Samples 32 | 33 | 34 | 35 | ### Express 36 | 37 | View the [source code](https://github.com/googleapis/nodejs-logging-bunyan/blob/main/samples/express.js). 38 | 39 | [![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) 40 | 41 | __Usage:__ 42 | 43 | 44 | `node samples/express.js` 45 | 46 | 47 | ----- 48 | 49 | 50 | 51 | 52 | ### Quickstart 53 | 54 | View the [source code](https://github.com/googleapis/nodejs-logging-bunyan/blob/main/samples/quickstart.js). 55 | 56 | [![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) 57 | 58 | __Usage:__ 59 | 60 | 61 | `node samples/quickstart.js` 62 | 63 | 64 | ----- 65 | 66 | 67 | 68 | 69 | ### Explict Auth Setup 70 | 71 | View the [source code](https://github.com/googleapis/nodejs-logging-bunyan/blob/main/samples/setup_explicit.js). 72 | 73 | [![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) 74 | 75 | __Usage:__ 76 | 77 | 78 | `node samples/setup_explicit.js` 79 | 80 | 81 | 82 | 83 | 84 | 85 | [shell_img]: https://gstatic.com/cloudssh/images/open-btn.png 86 | [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 87 | [product-docs]: https://cloud.google.com/logging 88 | -------------------------------------------------------------------------------- /samples/express.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | // [START logging_bunyan_express] 18 | // Imports the Google Cloud client library for Bunyan. 19 | const lb = require('@google-cloud/logging-bunyan'); 20 | 21 | // Import express module and create an http server. 22 | const express = require('express'); 23 | 24 | async function startServer() { 25 | const {logger, mw} = await lb.express.middleware({ 26 | logName: 'samples_express', 27 | }); 28 | const app = express(); 29 | 30 | // Install the logging middleware. This ensures that a Bunyan-style `log` 31 | // function is available on the `request` object. This should be the very 32 | // first middleware you attach to your app. 33 | app.use(mw); 34 | 35 | // Setup an http route and a route handler. 36 | app.get('/', (req, res) => { 37 | // `req.log` can be used as a bunyan style log method. All logs generated 38 | // using `req.log` use the current request context. That is, all logs 39 | // corresponding to a specific request will be bundled in the Stackdriver 40 | // UI. 41 | req.log.info('this is an info log message'); 42 | res.send('hello world'); 43 | }); 44 | 45 | const port = process.env.PORT || 8080; 46 | 47 | // `logger` can be used as a global logger, one not correlated to any specific 48 | // request. 49 | logger.info({port}, 'bonjour'); 50 | 51 | // Start listening on the http server. 52 | const server = app.listen(port, () => { 53 | console.log(`http server listening on port ${port}`); 54 | }); 55 | 56 | app.get('/shutdown', (req, res) => { 57 | res.sendStatus(200); 58 | server.close(); 59 | }); 60 | } 61 | 62 | startServer(); 63 | // [END logging_bunyan_express] 64 | -------------------------------------------------------------------------------- /samples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs-docs-samples-logging-bunyan", 3 | "files": [ 4 | "*.js", 5 | "!test/" 6 | ], 7 | "private": true, 8 | "license": "Apache-2.0", 9 | "author": "Google Inc.", 10 | "repository": "googleapis/nodejs-logging-bunyan", 11 | "engines": { 12 | "node": ">=14.0.0" 13 | }, 14 | "scripts": { 15 | "test": "mocha system-test --timeout 600000" 16 | }, 17 | "dependencies": { 18 | "@google-cloud/logging-bunyan": "^5.1.0", 19 | "bunyan": "^1.8.12", 20 | "express": "^4.16.3", 21 | "yargs": "^16.0.0" 22 | }, 23 | "devDependencies": { 24 | "@google-cloud/logging": "^11.0.0", 25 | "chai": "^4.2.0", 26 | "delay": "^5.0.0", 27 | "got": "^11.0.0", 28 | "mocha": "^8.0.0" 29 | } 30 | } -------------------------------------------------------------------------------- /samples/quickstart.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | // [START logging_bunyan_quickstart] 18 | const bunyan = require('bunyan'); 19 | 20 | // Imports the Google Cloud client library for Bunyan 21 | const {LoggingBunyan} = require('@google-cloud/logging-bunyan'); 22 | 23 | // Creates a Bunyan Cloud Logging client 24 | const loggingBunyan = new LoggingBunyan(); 25 | 26 | // Create a Bunyan logger that streams to Cloud Logging 27 | // Logs will be written to: "projects/YOUR_PROJECT_ID/logs/bunyan_log" 28 | const logger = bunyan.createLogger({ 29 | // The JSON payload of the log as it appears in Cloud Logging 30 | // will contain "name": "my-service" 31 | name: 'my-service', 32 | streams: [ 33 | // Log to the console at 'info' and above 34 | {stream: process.stdout, level: 'info'}, 35 | // And log to Cloud Logging, logging at 'info' and above 36 | loggingBunyan.stream('info'), 37 | ], 38 | }); 39 | 40 | // Writes some log entries 41 | logger.error('warp nacelles offline'); 42 | logger.info('shields at 99%'); 43 | // [END logging_bunyan_quickstart] 44 | -------------------------------------------------------------------------------- /samples/setup_explicit.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | // sample-metadata: 18 | // title: Explict Auth Setup 19 | 20 | /* eslint-disable no-unused-vars */ 21 | // [START logging_bunyan_setup_explicit] 22 | // Imports the Google Cloud client library for Bunyan 23 | const {LoggingBunyan} = require('@google-cloud/logging-bunyan'); 24 | 25 | // Creates a client 26 | const loggingBunyan = new LoggingBunyan({ 27 | projectId: 'your-project-id', 28 | keyFilename: '/path/to/key.json', 29 | }); 30 | // [END logging_bunyan_setup_explicit] 31 | /* eslint-enable no-unused-vars */ 32 | -------------------------------------------------------------------------------- /samples/system-test/express.test.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | const path = require('path'); 18 | const {assert} = require('chai'); 19 | const {describe, it, before, after} = require('mocha'); 20 | const {spawn} = require('child_process'); 21 | const delay = require('delay'); 22 | const got = require('got'); 23 | const {Logging} = require('@google-cloud/logging'); 24 | const logging = new Logging(); 25 | 26 | const PORT = process.env.PORT || 8080; 27 | const lb = require('@google-cloud/logging-bunyan'); 28 | const {APP_LOG_SUFFIX} = lb.express; 29 | 30 | describe('express samples', () => { 31 | after(() => got(`http://localhost:${PORT}/shutdown`)); 32 | before(async () => { 33 | // Start the express server. 34 | spawn(process.execPath, ['express.js'], { 35 | cwd: path.join(__dirname, '..'), 36 | cleanup: true, // kill child process when parent exits. 37 | }).stdout.pipe(process.stdout); 38 | 39 | // Wait 10 seconds for initialization and for server to start listening. 40 | await delay(10 * 1000); 41 | }); 42 | 43 | it('should write using bunyan', async function () { 44 | this.retries(3); 45 | 46 | // Make an HTTP request to exercise a request logging path. 47 | await got(`http://localhost:${PORT}/`); 48 | 49 | // Wait 10 seconds for logs to be written to Cloud service. 50 | await delay(10 * 1000); 51 | 52 | // Make sure the log was written to Cloud Logging. 53 | const log = logging.log(`samples_express_${APP_LOG_SUFFIX}`); 54 | const entries = (await log.getEntries({pageSize: 1}))[0]; 55 | assert.strictEqual(entries.length, 1); 56 | const entry = entries[0]; 57 | assert.strictEqual('this is an info log message', entry.data.message); 58 | // Ensure that a functional logger ws configured with the sample: 59 | assert.ok(entry.metadata.trace, 'should have a trace property'); 60 | assert.match(entry.metadata.trace, /projects\/.*\/traces\/.*/); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /samples/system-test/quickstart.test.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | const path = require('path'); 18 | const {assert} = require('chai'); 19 | const {describe, it} = require('mocha'); 20 | const cp = require('child_process'); 21 | 22 | const execSync = cmd => cp.execSync(cmd, {encoding: 'utf-8'}); 23 | 24 | const cwd = path.join(__dirname, '..'); 25 | 26 | describe('quickstart samples', () => { 27 | it('should write using bunyan', async () => { 28 | const stdout = execSync('node quickstart.js', {cwd}); 29 | assert.match(stdout, /99%/); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2017 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import {Writable} from 'stream'; 18 | import * as express from './middleware/express'; 19 | import { 20 | setInstrumentationStatus, 21 | createDiagnosticEntry, 22 | } from '@google-cloud/logging/build/src/utils/instrumentation'; 23 | 24 | // Export the express middleware as 'express'. 25 | export {express}; 26 | 27 | import { 28 | Logging, 29 | detectServiceContext, 30 | Log, 31 | LogSync, 32 | Entry, 33 | } from '@google-cloud/logging'; 34 | 35 | import * as types from './types/core'; 36 | 37 | import {ApiResponseCallback} from '@google-cloud/logging/build/src/log'; 38 | import {LogSeverityFunctions} from '@google-cloud/logging/build/src/utils/log-common'; 39 | import {LogSyncOptions} from '@google-cloud/logging/build/src/log-sync'; 40 | 41 | // Map of Stackdriver logging levels. 42 | const BUNYAN_TO_STACKDRIVER: Map = new Map([ 43 | [60, 'CRITICAL'], 44 | [50, 'ERROR'], 45 | [40, 'WARNING'], 46 | [30, 'INFO'], 47 | [20, 'DEBUG'], 48 | [10, 'DEBUG'], 49 | ]); 50 | 51 | /** 52 | * Key to use in the Bunyan payload to allow users to indicate a trace for the 53 | * request, and to store as an intermediate value on the log entry before it 54 | * gets written to the Cloud Logging logging API. 55 | */ 56 | export const LOGGING_TRACE_KEY = 'logging.googleapis.com/trace'; 57 | 58 | /** 59 | * Key to use in the Bunyan payload to allow users to indicate a spanId for the 60 | * request, and to store as an intermediate value on the log entry before it 61 | * gets written to the Cloud logging API. 62 | */ 63 | export const LOGGING_SPAN_KEY = 'logging.googleapis.com/spanId'; 64 | 65 | /** 66 | * Key to use in the Bunyan payload to allow users to indicate a traceSampled 67 | * flag for the request, and to store as an intermediate value on the log entry 68 | * before it gets written to the Cloud logging API. 69 | */ 70 | export const LOGGING_SAMPLED_KEY = 'logging.googleapis.com/trace_sampled'; 71 | 72 | /** 73 | * Default library version to be used 74 | * Using release-please annotations to update DEFAULT_INSTRUMENTATION_VERSION with latest version. 75 | * See https://github.com/googleapis/release-please/blob/main/docs/customizing.md#updating-arbitrary-files 76 | */ 77 | export const NODEJS_BUNYAN_DEFAULT_LIBRARY_VERSION = '5.1.0'; // {x-release-please-version} 78 | 79 | /** 80 | * Gets the current fully qualified trace ID when available from the 81 | * @google-cloud/trace-agent library in the LogEntry.trace field format of: 82 | * "projects/[PROJECT-ID]/traces/[TRACE-ID]". 83 | */ 84 | export function getCurrentTraceFromAgent() { 85 | const agent = global._google_trace_agent; 86 | if (!agent || !agent.getCurrentContextId || !agent.getWriterProjectId) { 87 | return null; 88 | } 89 | 90 | const traceId = agent.getCurrentContextId(); 91 | if (!traceId) { 92 | return null; 93 | } 94 | 95 | const traceProjectId = agent.getWriterProjectId(); 96 | if (!traceProjectId) { 97 | return null; 98 | } 99 | 100 | return `projects/${traceProjectId}/traces/${traceId}`; 101 | } 102 | 103 | /** 104 | * This module provides support for streaming your Bunyan logs to 105 | * [Stackdriver Logging](https://cloud.google.com/logging). 106 | * 107 | * @class 108 | * 109 | * @param {object} [options] 110 | * @param {string} [options.logName] The name of the log that will receive 111 | * messages written to this bunyan stream. Default: `bunyan_Log`. 112 | * @param {object} [options.resource] The monitored resource that the log 113 | * stream corresponds to. On Google Cloud Platform, this is detected 114 | * automatically, but you may optionally specify a specific monitored 115 | * resource. For more information, see the 116 | * [official documentation]{@link 117 | * https://cloud.google.com/monitoring/api/ref_v3/rpc/google.api#google.api.MonitoredResource} 118 | * 119 | * @param {object} [options.serviceContext] For logged errors, we provide this 120 | * as the service context. For more information see 121 | * [this guide]{@link 122 | * https://cloud.google.com/error-reporting/docs/formatting-error-messages} and 123 | * the [official documentation]{@link 124 | * https://cloud.google.com/error-reporting/reference/rest/v1beta1/ServiceContext}. 125 | * @param {string} [options.serviceContext.service] An identifier of the 126 | * service, such as the name of the executable, job, or Google App Engine 127 | * service name. 128 | * @param {string} [options.serviceContext.version] Represents the version of 129 | * the service. 130 | * @param {string} [options.projectId] The project ID from the Google Cloud 131 | * Console, e.g. 'grape-spaceship-123'. We will also check the environment 132 | * variable `GCLOUD_PROJECT` for your project ID. If your app is running in 133 | * an environment which supports {@link 134 | * https://cloud.google.com/docs/authentication/production#providing_credentials_to_your_application 135 | * Application Default Credentials}, your project ID will be detected 136 | * automatically. 137 | * @param {string} [options.keyFilename] Full path to the a .json, .pem, or .p12 138 | * key downloaded from the Google Cloud Console. If you provide a path 139 | * to a JSON file, the `projectId` option above is not necessary. NOTE: .pem 140 | * and .p12 require you to specify the `email` option as well. 141 | * @param {string} [options.email] Account email address. Required when using a 142 | * .pem or .p12 keyFilename. 143 | * @param {object} [options.credentials] Credentials object. 144 | * @param {string} [options.credentials.client_email] 145 | * @param {string} [options.credentials.private_key] 146 | * @param {boolean} [options.autoRetry=true] Automatically retry requests if the 147 | * response is related to rate limits or certain intermittent server errors. 148 | * We will exponentially backoff subsequent requests by default. 149 | * @param {number} [options.maxRetries=3] Maximum number of automatic retries 150 | * attempted before returning the error. 151 | * @param {constructor} [options.promise] Custom promise module to use instead 152 | * of native Promises. 153 | * @param {constructor} [options.promise] Custom promise module to use instead 154 | * of native Promises. 155 | * @param {number} [options.maxEntrySize] Max size limit of a log entry. 156 | * @param {string[]} [options.jsonFieldsToTruncate] A list of JSON properties at the given full path to be truncated. 157 | * @example Import the client library 158 | * ``` 159 | * const {LoggingBunyan} = require('@google-cloud/logging-bunyan'); 160 | * 161 | * ``` 162 | * @example Create a client that uses Application Default Credentials (ADC): 163 | * ``` 164 | * const loggingBunyan = new 165 | * LoggingBunyan(); 166 | * 167 | * ``` 168 | * @example Create a client with explicit credentials: 169 | * ``` 170 | * const loggingBunyan = new LoggingBunyan({ 171 | * projectId: 'your-project-id', 172 | * keyFilename: '/path/to/keyfile.json' 173 | * }); 174 | * 175 | * ``` 176 | * @example include:samples/quickstart.js 177 | * region_tag:logging_bunyan_quickstart 178 | * Full quickstart example: 179 | * 180 | */ 181 | export class LoggingBunyan extends Writable { 182 | private logName: string; 183 | private resource: types.MonitoredResource | undefined; 184 | private serviceContext?: types.ServiceContext; 185 | private defaultCallback?: ApiResponseCallback; 186 | cloudLog: LogSeverityFunctions; 187 | redirectToStdout: boolean; 188 | 189 | constructor(options?: types.Options) { 190 | options = options || {}; 191 | super({objectMode: true}); 192 | this.logName = options.logName || 'bunyan_log'; 193 | this.resource = options.resource; 194 | this.serviceContext = options.serviceContext; 195 | this.defaultCallback = options.defaultCallback; 196 | this.redirectToStdout = options.redirectToStdout ?? false; 197 | if (!this.redirectToStdout) { 198 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 199 | this.cloudLog = new Logging(options).log(this.logName, { 200 | removeCircular: true, 201 | // See: https://cloud.google.com/logging/quotas, a log size of 202 | // 250,000 has been chosen to keep us comfortably within the 203 | // 256,000 limit. 204 | maxEntrySize: options.maxEntrySize || 250000, 205 | jsonFieldsToTruncate: options.jsonFieldsToTruncate, 206 | }); 207 | } else { 208 | const logSyncOptions: LogSyncOptions = { 209 | useMessageField: options.useMessageField ?? true, 210 | }; 211 | this.cloudLog = new Logging(options).logSync( 212 | this.logName, 213 | undefined, 214 | logSyncOptions 215 | ); 216 | } 217 | 218 | // serviceContext.service is required by the Error Reporting 219 | // API. Without it, errors that are logged with level 'error' 220 | // or higher will not be displayed in the Error Reporting 221 | // console. 222 | // 223 | // As a result, if serviceContext is specified, it is required 224 | // that serviceContext.service is specified. 225 | if (this.serviceContext && !this.serviceContext.service) { 226 | throw new Error( 227 | "If 'serviceContext' is specified then " + 228 | "'serviceContext.service' is required." 229 | ); 230 | } 231 | 232 | /* Asynchrnously attempt to discover the service context. */ 233 | if (!this.serviceContext) { 234 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 235 | detectServiceContext((this.cloudLog as any).logging.auth).then( 236 | serviceContext => { 237 | this.serviceContext = serviceContext!; 238 | }, 239 | () => { 240 | /* swallow any errors. */ 241 | } 242 | ); 243 | } 244 | } 245 | 246 | /** 247 | * Convenience method that Builds a bunyan stream object that you can put in 248 | * the bunyan streams list. 249 | */ 250 | stream(level: types.LogLevel): types.StreamResponse { 251 | return {level, type: 'raw', stream: this as Writable}; 252 | } 253 | 254 | /** 255 | * Format a bunyan record into a Stackdriver log entry. 256 | */ 257 | private formatEntry_(record: string | types.BunyanLogRecord) { 258 | if (typeof record === 'string') { 259 | throw new Error( 260 | '@google-cloud/logging-bunyan only works as a raw bunyan stream type.' 261 | ); 262 | } 263 | // Stackdriver Log Viewer picks up the summary line from the 'message' field 264 | // of the payload. Unless the user has provided a 'message' property also, 265 | // move the 'msg' to 'message'. 266 | if (!record.message) { 267 | // If this is an error, report the full stack trace. This allows 268 | // Stackdriver Error Reporting to pick up errors automatically (for 269 | // severity 'error' or higher). In this case we leave the 'msg' property 270 | // intact. 271 | // https://cloud.google.com/error-reporting/docs/formatting-error-messages 272 | // 273 | if (record.err && record.err.stack) { 274 | record.message = record.err.stack; 275 | record.serviceContext = this.serviceContext; 276 | } else if (record.msg) { 277 | // Simply rename `msg` to `message`. 278 | record.message = record.msg; 279 | delete record.msg; 280 | } 281 | } 282 | 283 | const entryMetadata: types.StackdriverEntryMetadata = { 284 | resource: this.resource, 285 | timestamp: record.time, 286 | severity: BUNYAN_TO_STACKDRIVER.get(Number(record.level)), 287 | }; 288 | 289 | // If the record contains a httpRequest property, provide it on the entry 290 | // metadata. This allows Stackdriver to use request log formatting. 291 | // https://cloud.google.com/logging/docs/reference/v2/rpc/google.logging.type#google.logging.type.HttpRequest 292 | // Note that the httpRequest field must properly validate as a HttpRequest 293 | // proto message, or the log entry would be rejected by the API. We do no 294 | // validation here. 295 | if (record.httpRequest) { 296 | entryMetadata.httpRequest = record.httpRequest; 297 | delete record.httpRequest; 298 | } 299 | 300 | // If the record contains a labels property, promote it to the entry 301 | // metadata. 302 | // https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry 303 | const proper = LoggingBunyan.properLabels(record.labels); 304 | if (record.labels && proper) { 305 | entryMetadata.labels = record.labels; 306 | delete record.labels; 307 | } 308 | 309 | if (record[LOGGING_TRACE_KEY]) { 310 | entryMetadata.trace = record[LOGGING_TRACE_KEY]; 311 | delete record[LOGGING_TRACE_KEY]; 312 | } 313 | 314 | if (record[LOGGING_SPAN_KEY]) { 315 | entryMetadata.spanId = record[LOGGING_SPAN_KEY]; 316 | delete record[LOGGING_SPAN_KEY]; 317 | } 318 | 319 | if (LOGGING_SAMPLED_KEY in record) { 320 | entryMetadata.traceSampled = record[LOGGING_SAMPLED_KEY]; 321 | delete record[LOGGING_SAMPLED_KEY]; 322 | } 323 | 324 | return ( 325 | ( 326 | this.redirectToStdout 327 | ? (this.cloudLog as LogSync) 328 | : (this.cloudLog as Log) 329 | ) 330 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 331 | .entry(entryMetadata as any, record) 332 | ); 333 | } 334 | 335 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 336 | static properLabels(labels: any) { 337 | if (typeof labels !== 'object') return false; 338 | for (const prop in labels) { 339 | if (typeof labels[prop] !== 'string') { 340 | return false; 341 | } 342 | } 343 | return true; 344 | } 345 | 346 | /** 347 | * Intercept log entries as they are written so we can attempt to add the 348 | * trace ID in the same continuation as the function that wrote the log, 349 | * because the trace agent currently uses continuation local storage for the 350 | * trace context. 351 | * 352 | * By the time the Writable stream buffer gets flushed and _write gets called 353 | * we may well be in a different continuation. 354 | */ 355 | write(record: types.BunyanLogRecord, callback?: Function): boolean; 356 | write( 357 | record: types.BunyanLogRecord, 358 | encoding?: string, 359 | callback?: Function 360 | ): boolean; 361 | // Writable.write used 'any' in function signature. 362 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 363 | write(...args: any[]): boolean { 364 | let record = args[0]; 365 | let encoding: string | null = null; 366 | type Callback = (error: Error | null | undefined) => void; 367 | let callback: Callback | string; 368 | if (typeof args[1] === 'string') { 369 | encoding = args[1]; 370 | callback = args[2]; 371 | } else { 372 | callback = args[1]; 373 | } 374 | record = Object.assign({}, record); 375 | if (!record[LOGGING_TRACE_KEY]) { 376 | const trace = getCurrentTraceFromAgent(); 377 | if (trace) { 378 | record[LOGGING_TRACE_KEY] = trace; 379 | } 380 | } 381 | if (encoding !== null) { 382 | return super.write.call( 383 | this, 384 | record, 385 | encoding as BufferEncoding, 386 | callback as Callback 387 | ); 388 | } else { 389 | return super.write.call(this, record, callback as BufferEncoding); 390 | } 391 | } 392 | 393 | /** 394 | * Relay a log entry to the logging agent. This is called by bunyan through 395 | * Writable#write. 396 | */ 397 | _write(record: types.BunyanLogRecord, encoding: string, callback: Function) { 398 | const entry = this.formatEntry_(record); 399 | this._writeCall(entry, callback); 400 | } 401 | 402 | /** 403 | * Relay an array of log entries to the logging agent. This is called by 404 | * bunyan through Writable#write. 405 | */ 406 | // Writable._write used 'any' in function signature. 407 | _writev( 408 | chunks: Array<{ 409 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 410 | chunk: any; 411 | encoding: string; 412 | }>, 413 | callback: Function 414 | ) { 415 | const entries = chunks.map( 416 | (request: { 417 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 418 | chunk: any; 419 | encoding: string; 420 | }) => { 421 | return this.formatEntry_(request.chunk); 422 | } 423 | ); 424 | this._writeCall(entries, callback); 425 | } 426 | 427 | /** 428 | * Creates a combined callback which calls the this.defaultCallback and the Writable.write supplied callback 429 | * @param callback The callback function provided by Writable 430 | * @returns Combined callback which executes both, this.defaultCallback and one supplied by Writable.write 431 | */ 432 | generateCallback(callback: Function): ApiResponseCallback { 433 | // Make sure that both callbacks are called in case if provided 434 | const newCallback: ApiResponseCallback = ( 435 | err: Error | null, 436 | apiResponse?: {} 437 | ) => { 438 | if (callback) { 439 | callback(err, apiResponse); 440 | } 441 | if (this.defaultCallback) { 442 | this.defaultCallback(err, apiResponse); 443 | } 444 | }; 445 | return newCallback; 446 | } 447 | 448 | /** 449 | * A helper function to make a write call 450 | * @param entries The entries to be written 451 | * @param callback The callback supplied by Writable.write 452 | */ 453 | _writeCall(entries: Entry | Entry[], callback: Function) { 454 | // First create instrumentation record if it is never written before 455 | const alreadyWritten = setInstrumentationStatus(true); 456 | if (!alreadyWritten) { 457 | let instrumentationEntry = createDiagnosticEntry( 458 | 'nodejs-bunyan', 459 | this.getNodejsLibraryVersion() 460 | ); 461 | // Update instrumentation record resource and logName 462 | instrumentationEntry.metadata.resource = this.resource; 463 | instrumentationEntry.metadata.severity = 'INFO'; 464 | instrumentationEntry = ( 465 | this.redirectToStdout 466 | ? (this.cloudLog as LogSync) 467 | : (this.cloudLog as Log) 468 | ).entry(instrumentationEntry.metadata, instrumentationEntry.data); 469 | entries = Array.isArray(entries) ? entries : [entries]; 470 | entries.push(instrumentationEntry); 471 | } 472 | if (this.redirectToStdout) { 473 | (this.cloudLog as LogSync).write(entries); 474 | // The LogSync class does not supports callback. However if callback provided and 475 | // if this.defaultCallback exists, we should call it 476 | this.generateCallback(callback)(null, undefined); 477 | } else { 478 | (this.cloudLog as Log).write(entries, this.generateCallback(callback)); 479 | } 480 | } 481 | 482 | /** 483 | * Method used to retrieve the current logging-bunyan library version stored in NODEJS_BUNYAN_DEFAULT_LIBRARY_VERSION 484 | * @returns The version of this library 485 | */ 486 | getNodejsLibraryVersion() { 487 | return NODEJS_BUNYAN_DEFAULT_LIBRARY_VERSION; 488 | } 489 | } 490 | 491 | module.exports.BUNYAN_TO_STACKDRIVER = BUNYAN_TO_STACKDRIVER; 492 | 493 | /** 494 | * Value: `logging.googleapis.com/trace` 495 | * 496 | * @name LoggingBunyan.LOGGING_TRACE_KEY 497 | * @type {string} 498 | */ 499 | module.exports.LOGGING_TRACE_KEY = LOGGING_TRACE_KEY; 500 | 501 | /** 502 | * Value: `logging.googleapis.com/spanId` 503 | * 504 | * @name LoggingBunyan.LOGGING_SPAN_KEY 505 | * @type {string} 506 | */ 507 | module.exports.LOGGING_SPAN_KEY = LOGGING_SPAN_KEY; 508 | 509 | /** 510 | * Value: `logging.googleapis.com/trace_sampled` 511 | * 512 | * @name LoggingBunyan.LOGGING_SAMPLED_KEY 513 | * @type {string} 514 | */ 515 | module.exports.LOGGING_SAMPLED_KEY = LOGGING_SAMPLED_KEY; 516 | -------------------------------------------------------------------------------- /src/middleware/express.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | import { 17 | HttpRequest, 18 | middleware as commonMiddleware, 19 | } from '@google-cloud/logging'; 20 | import * as bunyan from 'bunyan'; 21 | import {GCPEnv} from 'google-auth-library'; 22 | 23 | import { 24 | LOGGING_TRACE_KEY, 25 | LOGGING_SPAN_KEY, 26 | LOGGING_SAMPLED_KEY, 27 | LoggingBunyan, 28 | } from '../index'; 29 | import * as types from '../types/core'; 30 | 31 | export const APP_LOG_SUFFIX = 'applog'; 32 | 33 | // @types/bunyan doesn't export Logger. Access it via ReturnType on 34 | // createLogger. 35 | export type Logger = ReturnType; 36 | 37 | export interface MiddlewareOptions extends types.Options { 38 | level?: types.LogLevel; 39 | // 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 40 | // that all the request-specific logs ("app logs") will nest under. GAE and GCF generate the parent request log automatically. Cloud Run 41 | // 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 42 | // introduced to enable this same behavior for Cloud Run. This might become the default behavior in a future major version. 43 | skipParentEntryForCloudRun?: boolean; 44 | } 45 | 46 | export interface MiddlewareReturnType { 47 | logger: Logger; 48 | mw: ReturnType; 49 | } 50 | 51 | /** 52 | * Express middleware 53 | */ 54 | export async function middleware( 55 | options?: MiddlewareOptions 56 | ): Promise { 57 | const defaultOptions = {logName: 'bunyan_log', level: 'info'}; 58 | options = Object.assign({}, defaultOptions, options); 59 | 60 | const loggingBunyanApp = new LoggingBunyan( 61 | Object.assign({}, options, { 62 | // For request bundling to work, the parent (request) and child (app) logs 63 | // need to have distinct names. For exact requirements see: 64 | // https://cloud.google.com/appengine/articles/logging#linking_app_logs_and_requests 65 | logName: `${options.logName}_${APP_LOG_SUFFIX}`, 66 | }) 67 | ); 68 | const logger = bunyan.createLogger({ 69 | name: `${options.logName}_${APP_LOG_SUFFIX}`, 70 | streams: [loggingBunyanApp.stream(options.level as types.LogLevel)], 71 | }); 72 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 73 | const auth = (loggingBunyanApp.cloudLog as any).logging.auth; 74 | const [env, projectId] = await Promise.all([ 75 | auth.getEnv(), 76 | auth.getProjectId(), 77 | ]); 78 | 79 | // Unless we are running on Google App Engine or Cloud Functions, generate a 80 | // parent request log entry that all the request-specific logs ("app logs") 81 | // will nest under. GAE and GCF generate the parent request logs 82 | // automatically. 83 | // Cloud Run also generates the parent request log automatically, but skipping 84 | // the parent request entry has to be explicity enabled until the next major 85 | // release in which we can change the default behavior. 86 | let emitRequestLog; 87 | if ( 88 | env !== GCPEnv.APP_ENGINE && 89 | env !== GCPEnv.CLOUD_FUNCTIONS && 90 | (env !== GCPEnv.CLOUD_RUN || !options.skipParentEntryForCloudRun) 91 | ) { 92 | const loggingBunyanReq = new LoggingBunyan(options); 93 | const requestLogger = bunyan.createLogger({ 94 | name: options.logName!, 95 | streams: [loggingBunyanReq.stream(options.level as types.LogLevel)], 96 | }); 97 | emitRequestLog = ( 98 | httpRequest: HttpRequest, 99 | trace: string, 100 | span?: string, 101 | sampled?: boolean 102 | ) => { 103 | requestLogger.info({ 104 | [LOGGING_TRACE_KEY]: trace, 105 | [LOGGING_SPAN_KEY]: span, 106 | [LOGGING_SAMPLED_KEY]: sampled, 107 | httpRequest, 108 | }); 109 | }; 110 | } 111 | 112 | return { 113 | logger, 114 | mw: commonMiddleware.express.makeMiddleware( 115 | projectId, 116 | makeChildLogger, 117 | emitRequestLog 118 | ), 119 | }; 120 | 121 | function makeChildLogger(trace: string, span?: string) { 122 | return logger.child( 123 | {[LOGGING_TRACE_KEY]: trace, [LOGGING_SPAN_KEY]: span}, 124 | true /* simple child */ 125 | ); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/types/core.d.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2017 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import {ApiResponseCallback} from '@google-cloud/logging/build/src/log'; 18 | 19 | export interface Options { 20 | /** 21 | * The name of the log that will receive messages written to this bunyan 22 | * stream. Default: `bunyan_Log`. 23 | */ 24 | logName?: string; 25 | /** 26 | * The monitored resource that the log stream corresponds to. On Google Cloud 27 | * Platform, this is detected automatically, but you may optionally specify a 28 | * specific monitored resource. For more information, see the 29 | * [official documentation]{@link https://cloud.google.com/logging/docs/api/reference/rest/v2/MonitoredResource} 30 | */ 31 | resource?: MonitoredResource; 32 | /** 33 | * For logged errors, we provide this as the service context. For more 34 | * information see [this guide]{@link https://cloud.google.com/error-reporting/docs/formatting-error-messages} 35 | * and the [official documentation]{@link https://cloud.google.com/error-reporting/reference/rest/v1beta1/ServiceContext}. 36 | */ 37 | serviceContext?: ServiceContext; 38 | /** 39 | * The project ID from the Google Cloud 40 | * Console, e.g. 'grape-spaceship-123'. We will also check the environment 41 | * variable `GCLOUD_PROJECT` for your project ID. If your app is running in 42 | * an environment which supports {@link https://cloud.google.com/docs/authentication/production#providing_credentials_to_your_application Application Default Credentials}, 43 | * your project ID will be detected automatically. 44 | */ 45 | projectId?: string; 46 | /** 47 | * Full path to the a .json, .pem, or .p12 key downloaded from the Google 48 | * Cloud Console. If you provide a path to a JSON file, the `projectId` option 49 | * above is not necessary. NOTE: .pem and .p12 require you to specify the 50 | * `email` option as well. 51 | */ 52 | keyFilename?: string; 53 | /** 54 | * Account email address. Required when using a .pem or .p12 keyFilename. 55 | */ 56 | email?: string; 57 | /** 58 | * Credentials object. 59 | */ 60 | credentials?: Credentials; 61 | /** 62 | * Automatically retry requests if the response is related to rate limits or 63 | * certain intermittent server errors. We will exponentially backoff 64 | * subsequent requests by default. 65 | */ 66 | autoRetry?: boolean; 67 | /** 68 | * Maximum number of automatic retries attempted before returning the error. 69 | */ 70 | maxRetries?: number; 71 | /** 72 | * Custom promise module to use instead of native Promises. 73 | */ 74 | // TODO: address the correct type of promise. 75 | promise?: {}; 76 | /** 77 | * The host name of the service to send requests. 78 | * Defaults to `logging.googleapis.com`. 79 | */ 80 | apiEndpoint?: string; 81 | /** 82 | * An attempt will be made to truncate messages larger than maxEntrySize. 83 | * Please note that this parameter is ignored when redirectToStdout is set. 84 | */ 85 | maxEntrySize?: number; 86 | /** 87 | * A list of JSON properties at the given full path to be truncated. 88 | * Received values will be prepended to predefined list in the order received and duplicates discarded. 89 | */ 90 | jsonFieldsToTruncate?: string[]; 91 | // A default global callback to be used for {@link LoggingBunyan} write calls 92 | // when callback is not supplied by caller in function parameters 93 | defaultCallback?: ApiResponseCallback; 94 | /** 95 | * Boolen flag that opts-in redirecting the output to STDOUT instead of ingesting logs to Cloud 96 | * Logging using Logging API. Defaults to {@code false}. Redirecting logs can be used in 97 | * Google Cloud environments with installed logging agent to delegate log ingestions to the 98 | * agent. Redirected logs are formatted as one line Json string following the structured logging guidelines. 99 | */ 100 | redirectToStdout?: boolean; 101 | 102 | /** 103 | * Boolean flag indicating if "message" field should be used to store structured, 104 | * non-text data inside jsonPayload field. This flag applies only when {@link Options#redirectToStdout} is set. 105 | * By default this value is true 106 | */ 107 | useMessageField?: boolean; 108 | } 109 | 110 | export interface MonitoredResource { 111 | type?: string; 112 | labels?: {[key: string]: string}; 113 | } 114 | 115 | export interface ServiceContext { 116 | /** 117 | * An identifier of the service, such as the name of the executable, job, or 118 | * Google App Engine service name. 119 | */ 120 | service?: string; 121 | /** 122 | * Represents the version of the service. 123 | */ 124 | version?: string; 125 | } 126 | 127 | export interface StackdriverLog { 128 | critical: ( 129 | entry: StackdriverEntry | StackdriverEntry[], 130 | options?: {}, 131 | callback?: (err: Error, apiResponse: {}) => void 132 | ) => Promise; 133 | debug: ( 134 | entry: StackdriverEntry | StackdriverEntry[], 135 | options?: {}, 136 | callback?: (err: Error, apiResponse: {}) => void 137 | ) => Promise; 138 | emergency: ( 139 | entry: StackdriverEntry | StackdriverEntry[], 140 | options?: {}, 141 | callback?: (err: Error, apiResponse: {}) => void 142 | ) => Promise; 143 | error: ( 144 | entry: StackdriverEntry | StackdriverEntry[], 145 | options?: {}, 146 | callback?: (err: Error, apiResponse: {}) => void 147 | ) => Promise; 148 | info: ( 149 | entry: StackdriverEntry | StackdriverEntry[], 150 | options?: {}, 151 | callback?: (err: Error, apiResponse: {}) => void 152 | ) => Promise; 153 | notice: ( 154 | entry: StackdriverEntry | StackdriverEntry[], 155 | options?: {}, 156 | callback?: (err: Error, apiResponse: {}) => void 157 | ) => Promise; 158 | warning: ( 159 | entry: StackdriverEntry | StackdriverEntry[], 160 | options?: {}, 161 | callback?: (err: Error, apiResponse: {}) => void 162 | ) => Promise; 163 | write: ( 164 | entry: StackdriverEntry | StackdriverEntry[], 165 | options?: {}, 166 | callback?: (err: Error, apiResponse: {}) => void 167 | ) => Promise; 168 | alert: ( 169 | entry: StackdriverEntry | StackdriverEntry[], 170 | options?: {}, 171 | callback?: (err: Error, apiResponse: {}) => void 172 | ) => Promise; 173 | entry: (metadata: {}, data: {} | string) => StackdriverEntry; 174 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 175 | logging: any; 176 | } 177 | 178 | export interface Credentials { 179 | client_email?: string; 180 | private_key?: string; 181 | } 182 | 183 | export interface StackdriverEntryMetadata { 184 | resource?: MonitoredResource; 185 | timestamp?: Date; 186 | severity?: string; // figure out the correct type later 187 | httpRequest?: HttpRequest; 188 | labels?: {}; 189 | trace?: {}; 190 | spanId?: {}; 191 | traceSampled?: {}; 192 | } 193 | 194 | export interface StackdriverLog { 195 | critical: ( 196 | entry: StackdriverEntry | StackdriverEntry[], 197 | options?: {}, 198 | callback?: (err: Error, apiResponse: {}) => void 199 | ) => Promise; 200 | debug: ( 201 | entry: StackdriverEntry | StackdriverEntry[], 202 | options?: {}, 203 | callback?: (err: Error, apiResponse: {}) => void 204 | ) => Promise; 205 | emergency: ( 206 | entry: StackdriverEntry | StackdriverEntry[], 207 | options?: {}, 208 | callback?: (err: Error, apiResponse: {}) => void 209 | ) => Promise; 210 | error: ( 211 | entry: StackdriverEntry | StackdriverEntry[], 212 | options?: {}, 213 | callback?: (err: Error, apiResponse: {}) => void 214 | ) => Promise; 215 | info: ( 216 | entry: StackdriverEntry | StackdriverEntry[], 217 | options?: {}, 218 | callback?: (err: Error, apiResponse: {}) => void 219 | ) => Promise; 220 | notice: ( 221 | entry: StackdriverEntry | StackdriverEntry[], 222 | options?: {}, 223 | callback?: (err: Error, apiResponse: {}) => void 224 | ) => Promise; 225 | warning: ( 226 | entry: StackdriverEntry | StackdriverEntry[], 227 | options?: {}, 228 | callback?: (err: Error, apiResponse: {}) => void 229 | ) => Promise; 230 | write: ( 231 | entry: StackdriverEntry | StackdriverEntry[], 232 | options?: {}, 233 | callback?: (err: Error, apiResponse: {}) => void 234 | ) => Promise; 235 | alert: ( 236 | entry: StackdriverEntry | StackdriverEntry[], 237 | options?: {}, 238 | callback?: (err: Error, apiResponse: {}) => void 239 | ) => Promise; 240 | entry: (metadata: {}, data: {} | string) => StackdriverEntry; 241 | } 242 | 243 | export interface StackdriverLogging { 244 | Entry?: StackdriverEntry; 245 | Log?: StackdriverLog; 246 | Logging?: StackdriverLogging; 247 | entry?: ( 248 | resource?: MonitoredResource, 249 | data?: {message: string} | string 250 | ) => StackdriverEntry; 251 | // define additional properties and methods. 252 | } 253 | 254 | export interface StackdriverEntry { 255 | constructor: ( 256 | metadata?: StackdriverEntryMetadata, 257 | data?: {message: string} | string 258 | ) => StackdriverEntry; 259 | data?: StackdriverData | string; 260 | metadata?: StackdriverEntryMetadata; 261 | } 262 | 263 | export interface StackdriverData { 264 | serviceContext?: ServiceContext; 265 | message?: string; 266 | metadata?: Metadata; 267 | pid?: string; 268 | test?: {circular?: string}; 269 | } 270 | 271 | export interface Metadata { 272 | stack?: string; 273 | httpRequest?: HttpRequest; 274 | } 275 | 276 | type LogWriteResponse = {}[]; 277 | 278 | export interface HttpRequest { 279 | requestMethod?: string; 280 | requestUrl?: string; 281 | requestSize?: number; 282 | status?: number; 283 | responseSize?: number; 284 | userAgent?: string; 285 | remoteIp?: string; 286 | serverIp?: string; 287 | referer?: string; 288 | latency?: string | {seconds: number; nanos: number}; 289 | cacheLookup?: boolean; 290 | cacheHit?: boolean; 291 | cacheValidatedWithOriginServer?: boolean; 292 | cacheFillBytes?: number; 293 | protocol?: string; 294 | } 295 | 296 | export interface BunyanLogRecord { 297 | message?: string; 298 | msg?: string; 299 | err?: Error; 300 | serviceContext?: ServiceContext; 301 | level?: string; 302 | time?: Date; 303 | httpRequest?: HttpRequest; 304 | labels?: {}; 305 | // And arbitrary other properties. 306 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 307 | [key: string]: any; 308 | } 309 | 310 | export type LogLevel = 311 | | 'trace' 312 | | 'debug' 313 | | 'info' 314 | | 'warn' 315 | | 'error' 316 | | 'fatal' 317 | | number; 318 | 319 | export interface StreamResponse { 320 | level: LogLevel; 321 | type: string; 322 | stream: NodeJS.WritableStream; 323 | } 324 | -------------------------------------------------------------------------------- /src/types/global.d.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2018 Google LLC. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export {}; 18 | 19 | declare global { 20 | // eslint-disable-next-line 21 | var _google_trace_agent: any; 22 | } 23 | -------------------------------------------------------------------------------- /system-test/errors-transport.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as common from '@google-cloud/common'; 16 | import delay from 'delay'; 17 | 18 | // eslint-disable-next-line @typescript-eslint/no-var-requires 19 | const packageJson = require('../../package.json'); 20 | 21 | export interface ServiceContext { 22 | service: string; 23 | version: string; 24 | resourceType: string; 25 | } 26 | 27 | export interface ErrorEvent { 28 | eventTime: string; 29 | serviceContext: ServiceContext; 30 | message: string; 31 | // other fields not used in the tests have been omitted 32 | } 33 | 34 | export interface ErrorGroupStats { 35 | group: {groupId: string}; 36 | affectedServices: ServiceContext[]; 37 | representative: ErrorEvent; 38 | count: string; 39 | // other fields not used in the tests have been omitted 40 | } 41 | 42 | export interface ApiResponse { 43 | body: {errorGroupStats: ErrorGroupStats[]; errorEvents: ErrorEvent[]}; 44 | } 45 | 46 | /* @const {String} Base Error Reporting API */ 47 | const API = 'https://clouderrorreporting.googleapis.com/v1beta1/projects'; 48 | 49 | const ONE_HOUR_API = 'timeRange.period=PERIOD_1_HOUR'; 50 | 51 | export class ErrorsApiTransport extends common.Service { 52 | constructor() { 53 | super({ 54 | baseUrl: 'https://clouderrorreporting.googleapis.com/v1beta1', 55 | apiEndpoint: 'clouderrorreporting.googleapis.com', 56 | scopes: ['https://www.googleapis.com/auth/cloud-platform'], 57 | packageJson, 58 | }); 59 | } 60 | 61 | async request(options: common.DecorateRequestOptions) { 62 | return new Promise((resolve, reject) => { 63 | super.request(options, (err, _, res) => { 64 | return err ? reject(err) : resolve(res as common.ResponseBody); 65 | }); 66 | }); 67 | } 68 | 69 | async getAllGroups(): Promise { 70 | const projectId = await this.getProjectId(); 71 | const options = { 72 | uri: [API, projectId, 'groupStats?' + ONE_HOUR_API].join('/'), 73 | method: 'GET', 74 | }; 75 | const response = await this.request(options); 76 | return response.body.errorGroupStats || []; 77 | } 78 | 79 | async getGroupEvents(groupId: string): Promise { 80 | const projectId = await this.getProjectId(); 81 | const options = { 82 | uri: [ 83 | API, 84 | projectId, 85 | 'events?groupId=' + groupId + '&pageSize=10&' + ONE_HOUR_API, 86 | ].join('/'), 87 | method: 'GET', 88 | }; 89 | 90 | const response = await this.request(options); 91 | return response.body.errorEvents || []; 92 | } 93 | 94 | async pollForNewEvents( 95 | service: string, 96 | time: number, 97 | timeout: number 98 | ): Promise { 99 | const timeLimit = Date.now() + timeout; 100 | let groupId; 101 | // wait for a group 102 | while (Date.now() < timeLimit) { 103 | await delay(1000); 104 | 105 | if (!groupId) { 106 | const groups = await this.getAllGroups(); 107 | if (!groups.length) continue; 108 | // find an error group that matches the service 109 | groups.forEach(group => { 110 | const match = group.affectedServices.find( 111 | context => context.service === service 112 | ); 113 | if (match) { 114 | groupId = group.group.groupId; 115 | } 116 | }); 117 | } 118 | 119 | // didnt find an error reporting group matching the service. 120 | if (!groupId) continue; 121 | 122 | const events = await this.getGroupEvents(groupId); 123 | const filteredEvents = events.filter( 124 | event => 125 | event.serviceContext.service === service && 126 | new Date(event.eventTime).getTime() >= time 127 | ); 128 | if (filteredEvents.length) { 129 | return filteredEvents; 130 | } 131 | } 132 | return []; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /system-test/logging-bunyan.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2017 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import * as assert from 'assert'; 18 | import {describe, it} from 'mocha'; 19 | import * as bunyan from 'bunyan'; 20 | import * as uuid from 'uuid'; 21 | import * as types from '../src/types/core'; 22 | import {ErrorsApiTransport} from './errors-transport'; 23 | import {Logging, LogSync} from '@google-cloud/logging'; 24 | 25 | const logging = new Logging(); 26 | import {LoggingBunyan} from '../src/index'; 27 | import delay from 'delay'; 28 | import * as instrumentation from '@google-cloud/logging/build/src/utils/instrumentation'; 29 | 30 | const WRITE_CONSISTENCY_DELAY_MS = 90000; 31 | const MESSAGE = 'Diagnostic test'; 32 | 33 | const UUID = uuid.v4(); 34 | function logName(name: string) { 35 | return `${UUID}_${name}`; 36 | } 37 | 38 | describe('LoggingBunyan', function () { 39 | this.timeout(WRITE_CONSISTENCY_DELAY_MS); 40 | 41 | const SERVICE = `logging-bunyan-system-test-${UUID}`; 42 | const LOG_NAME = logName('logging-bunyan-system-test'); 43 | const loggingBunyan = new LoggingBunyan({ 44 | logName: LOG_NAME, 45 | serviceContext: {service: SERVICE, version: 'none'}, 46 | }); 47 | const logger = bunyan.createLogger({ 48 | name: 'google-cloud-node-system-test', 49 | streams: [loggingBunyan.stream('info')], 50 | }); 51 | 52 | it('should create LoggingBunyan with LogSync', () => { 53 | const loggingBunyan = new LoggingBunyan({ 54 | logName: LOG_NAME, 55 | redirectToStdout: true, 56 | }); 57 | assert.ok(loggingBunyan.cloudLog instanceof LogSync); 58 | }); 59 | 60 | it('should create LoggingBunyan with LogSync and useMessageField is off', () => { 61 | const loggingBunyan = new LoggingBunyan({ 62 | logName: LOG_NAME, 63 | redirectToStdout: true, 64 | useMessageField: false, 65 | }); 66 | assert.ok(loggingBunyan.cloudLog instanceof LogSync); 67 | assert.ok(loggingBunyan.cloudLog.useMessageField_ === false); 68 | }); 69 | 70 | it('should write diagnostic entry', async () => { 71 | instrumentation.setInstrumentationStatus(false); 72 | const start = Date.now(); 73 | logger.info(MESSAGE); 74 | const entries = await pollLogs( 75 | LOG_NAME, 76 | start, 77 | 2, 78 | WRITE_CONSISTENCY_DELAY_MS 79 | ); 80 | assert.strictEqual(entries.length, 2); 81 | let isDiagnosticPresent = false; 82 | entries.forEach(entry => { 83 | assert.ok(entry.data); 84 | if ( 85 | Object.prototype.hasOwnProperty.call( 86 | entry.data, 87 | instrumentation.DIAGNOSTIC_INFO_KEY 88 | ) 89 | ) { 90 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 91 | const info = (entry.data as any)[instrumentation.DIAGNOSTIC_INFO_KEY][ 92 | instrumentation.INSTRUMENTATION_SOURCE_KEY 93 | ]; 94 | assert.equal(info[0].name, 'nodejs-bunyan'); 95 | assert.ok(info[0].version.includes('.')); 96 | assert.equal(info[1].name, 'nodejs'); 97 | assert.ok(info[1].version.includes('.')); 98 | isDiagnosticPresent = true; 99 | } else { 100 | const data = entry.data as {message: string}; 101 | assert.ok(data.message.includes(MESSAGE)); 102 | } 103 | }); 104 | assert.ok(isDiagnosticPresent); 105 | }); 106 | 107 | it('should properly write log entries', async function () { 108 | this.retries(3); 109 | const timestamp = new Date(); 110 | const start = Date.now(); 111 | 112 | // Type of circular.circular cannot be determined.. 113 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 114 | const circular: {circular?: any} = {}; 115 | circular.circular = circular; 116 | 117 | const testData = [ 118 | { 119 | args: ['first'], 120 | level: 'info', 121 | verify: (entry: types.StackdriverEntry) => { 122 | assert.strictEqual( 123 | (entry.data as types.StackdriverData).message, 124 | 'first' 125 | ); 126 | assert.strictEqual( 127 | (entry.data as types.StackdriverData).pid, 128 | process.pid 129 | ); 130 | }, 131 | }, 132 | 133 | { 134 | args: [new Error('second')], 135 | level: 'error', 136 | verify: (entry: types.StackdriverEntry) => { 137 | assert( 138 | ((entry.data as types.StackdriverData).message as string).includes( 139 | 'Error: second' 140 | ) 141 | ); 142 | assert.strictEqual( 143 | (entry.data as types.StackdriverData).pid, 144 | process.pid 145 | ); 146 | }, 147 | }, 148 | 149 | { 150 | args: [ 151 | { 152 | test: circular, 153 | }, 154 | 'third', 155 | ], 156 | level: 'info', 157 | verify: (entry: types.StackdriverEntry) => { 158 | assert.strictEqual( 159 | (entry.data as types.StackdriverData).message, 160 | 'third' 161 | ); 162 | assert.strictEqual( 163 | (entry.data as types.StackdriverData).pid, 164 | process.pid 165 | ); 166 | assert.deepStrictEqual((entry.data as types.StackdriverData).test, { 167 | circular: '[Circular]', 168 | }); 169 | }, 170 | }, 171 | ]; 172 | 173 | const earliest = { 174 | args: [ 175 | { 176 | time: timestamp, 177 | }, 178 | 'earliest', 179 | ], 180 | level: 'info', 181 | verify: (entry: types.StackdriverEntry) => { 182 | assert.strictEqual( 183 | (entry.data as types.StackdriverData).message, 184 | 'earliest' 185 | ); 186 | assert.strictEqual( 187 | (entry.data as types.StackdriverData).pid, 188 | process.pid 189 | ); 190 | assert.strictEqual( 191 | ( 192 | (entry.metadata as types.StackdriverEntryMetadata).timestamp as Date 193 | ).toString(), 194 | timestamp.toString() 195 | ); 196 | }, 197 | }; 198 | 199 | // Forcibly insert a delay to cause 'third' to have a deterministically 200 | // earlier timestamp. 201 | await delay(10); 202 | 203 | testData.forEach(test => { 204 | // logger does not have index signature. 205 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 206 | (logger as any)[test.level].apply(logger, test.args); 207 | }); 208 | // `earliest` is sent last, but it should show up as the earliest entry. 209 | // logger does not have index signature. 210 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 211 | (logger as any)[earliest.level].apply(logger, earliest.args); 212 | // insert into list as the earliest entry. 213 | // TODO: identify the correct type for testData and earliest 214 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 215 | testData.unshift(earliest as any); 216 | 217 | const entries = await pollLogs( 218 | LOG_NAME, 219 | start, 220 | testData.length, 221 | WRITE_CONSISTENCY_DELAY_MS 222 | ); 223 | assert.strictEqual(entries.length, testData.length); 224 | entries.reverse().forEach((entry, index) => { 225 | const test = testData[index]; 226 | test.verify(entry); 227 | }); 228 | }); 229 | 230 | describe('ErrorReporting', () => { 231 | const ERROR_REPORTING_POLL_TIMEOUT = WRITE_CONSISTENCY_DELAY_MS; 232 | const errorsTransport = new ErrorsApiTransport(); 233 | 234 | it('reports errors when logging errors', async function () { 235 | this.retries(3); 236 | const start = Date.now(); 237 | 238 | const message = `an error at ${Date.now()}`; 239 | logger.error(new Error(message)); 240 | 241 | const errors = await errorsTransport.pollForNewEvents( 242 | SERVICE, 243 | start, 244 | ERROR_REPORTING_POLL_TIMEOUT 245 | ); 246 | 247 | assert.strictEqual( 248 | errors.length, 249 | 1, 250 | `expected 1 error but got ${require('util').inspect(errors)}` 251 | ); 252 | const errEvent = errors[0]; 253 | 254 | assert.strictEqual(errEvent.serviceContext.service, SERVICE); 255 | assert(errEvent.message.includes(`Error: ${message}`)); 256 | }); 257 | }); 258 | }); 259 | 260 | // polls for the entire array of entries to be greater than logTime. 261 | function pollLogs( 262 | logName: string, 263 | logTime: number, 264 | size: number, 265 | timeout: number 266 | ) { 267 | const p = new Promise((resolve, reject) => { 268 | const end = Date.now() + timeout; 269 | loop(); 270 | 271 | function loop() { 272 | setTimeout(() => { 273 | logging.log(logName).getEntries( 274 | { 275 | pageSize: size, 276 | }, 277 | (err, entries) => { 278 | if (!entries || entries.length < size) return loop(); 279 | 280 | const {receiveTimestamp} = (entries[entries.length - 1].metadata || 281 | {}) as {receiveTimestamp: {seconds: number; nanos: number}}; 282 | const timeMilliseconds = 283 | receiveTimestamp.seconds * 1000 + receiveTimestamp.nanos * 1e-6; 284 | 285 | if (timeMilliseconds >= logTime) { 286 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 287 | return resolve(entries as any); 288 | } 289 | 290 | if (Date.now() > end) { 291 | return reject(new Error('timeout')); 292 | } 293 | loop(); 294 | } 295 | ); 296 | }, 500); 297 | } 298 | }); 299 | 300 | return p; 301 | } 302 | -------------------------------------------------------------------------------- /system-test/test-install.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as check from 'post-install-check'; 16 | 17 | const TS_CODE_ARRAY: check.CodeSample[] = [ 18 | { 19 | code: `import * as lb from '@google-cloud/logging-bunyan'; 20 | import * as bunyan from 'bunyan'; 21 | 22 | const loggingBunyan = new lb.LoggingBunyan(); 23 | 24 | bunyan.createLogger({ 25 | name: 'my-service', 26 | level: 'info', 27 | streams: [ 28 | {stream: process.stdout}, 29 | loggingBunyan.stream('info'), 30 | ], 31 | });`, 32 | description: 'imports the module using * syntax', 33 | dependencies: ['bunyan'], 34 | devDependencies: ['@types/bunyan'], 35 | }, 36 | { 37 | code: `import {LoggingBunyan} from '@google-cloud/logging-bunyan'; 38 | import * as bunyan from 'bunyan'; 39 | 40 | const loggingBunyan = new LoggingBunyan(); 41 | 42 | bunyan.createLogger({ 43 | name: 'my-service', 44 | level: 'info', 45 | streams: [ 46 | {stream: process.stdout}, 47 | loggingBunyan.stream('info'), 48 | ], 49 | });`, 50 | description: 'imports the module with {} syntax', 51 | dependencies: ['bunyan'], 52 | devDependencies: ['@types/bunyan'], 53 | }, 54 | { 55 | code: `import {LoggingBunyan} from '@google-cloud/logging-bunyan'; 56 | import * as bunyan from 'bunyan'; 57 | 58 | const loggingBunyan = new LoggingBunyan({ 59 | serviceContext: { 60 | service: 'some service' 61 | } 62 | }); 63 | 64 | bunyan.createLogger({ 65 | name: 'my-service', 66 | level: 'info', 67 | streams: [ 68 | {stream: process.stdout}, 69 | loggingBunyan.stream('info'), 70 | ], 71 | });`, 72 | description: 73 | 'imports the module and starts with a partial `serviceContext`', 74 | dependencies: ['bunyan'], 75 | devDependencies: ['@types/bunyan'], 76 | }, 77 | { 78 | code: `import {LoggingBunyan} from '@google-cloud/logging-bunyan'; 79 | import * as bunyan from 'bunyan'; 80 | 81 | const loggingBunyan = new LoggingBunyan({ 82 | projectId: 'some-project', 83 | serviceContext: { 84 | service: 'Some service', 85 | version: 'Some version' 86 | } 87 | }); 88 | 89 | bunyan.createLogger({ 90 | name: 'my-service', 91 | level: 'info', 92 | streams: [ 93 | {stream: process.stdout}, 94 | loggingBunyan.stream('info'), 95 | ], 96 | });`, 97 | description: 98 | 'imports the module and starts with a complete `serviceContext`', 99 | dependencies: ['bunyan'], 100 | devDependencies: ['@types/bunyan'], 101 | }, 102 | { 103 | code: `import * as lb from '@google-cloud/logging-bunyan'; 104 | import * as express from 'express'; 105 | 106 | async function main() { 107 | const {logger, mw} = await lb.express.middleware(); 108 | const app = express(); 109 | app.use(mw); 110 | }`, 111 | description: 'can be used with express', 112 | dependencies: ['express', 'bunyan'], 113 | devDependencies: ['@types/bunyan', '@types/express'], 114 | }, 115 | ]; 116 | 117 | const JS_CODE_ARRAY: check.CodeSample[] = [ 118 | { 119 | code: `const LoggingBunyan = require('@google-cloud/logging-bunyan').LoggingBunyan; 120 | const bunyan = require('bunyan'); 121 | 122 | const loggingBunyan = new LoggingBunyan(); 123 | 124 | bunyan.createLogger({ 125 | name: 'my-service', 126 | level: 'info', 127 | streams: [ 128 | {stream: process.stdout}, 129 | loggingBunyan.stream('info'), 130 | ], 131 | });`, 132 | description: 'requires the module using Node 4+ syntax', 133 | dependencies: ['bunyan'], 134 | devDependencies: [], 135 | }, 136 | { 137 | code: `const LoggingBunyan = require('@google-cloud/logging-bunyan').LoggingBunyan; 138 | const bunyan = require('bunyan'); 139 | 140 | const loggingBunyan = new LoggingBunyan({ 141 | serviceContext: { 142 | service: 'some service' 143 | } 144 | }); 145 | 146 | bunyan.createLogger({ 147 | name: 'my-service', 148 | level: 'info', 149 | streams: [ 150 | {stream: process.stdout}, 151 | loggingBunyan.stream('info'), 152 | ], 153 | });`, 154 | description: 155 | 'requires the module and starts with a partial `serviceContext`', 156 | dependencies: ['bunyan'], 157 | devDependencies: [], 158 | }, 159 | { 160 | code: `const LoggingBunyan = require('@google-cloud/logging-bunyan').LoggingBunyan; 161 | const bunyan = require('bunyan'); 162 | 163 | const loggingBunyan = new LoggingBunyan({ 164 | projectId: 'some-project', 165 | serviceContext: { 166 | service: 'Some service', 167 | version: 'Some version' 168 | } 169 | }); 170 | 171 | bunyan.createLogger({ 172 | name: 'my-service', 173 | level: 'info', 174 | streams: [ 175 | {stream: process.stdout}, 176 | loggingBunyan.stream('info'), 177 | ], 178 | });`, 179 | description: 180 | 'requires the module and starts with a complete `serviceContext`', 181 | dependencies: ['bunyan'], 182 | devDependencies: [], 183 | }, 184 | { 185 | code: `const lb = require('@google-cloud/logging-bunyan'); 186 | const express = require('express'); 187 | 188 | async function main() { 189 | lb.express.middleware().then(result => { 190 | const app = express(); 191 | app.use(result.mw); 192 | }).catch((err) => { 193 | console.error(err); 194 | process.exit(1); 195 | }); 196 | }`, 197 | description: 'can be used with express', 198 | dependencies: ['express', 'bunyan'], 199 | devDependencies: [], 200 | }, 201 | ]; 202 | 203 | check.testInstallation(TS_CODE_ARRAY, JS_CODE_ARRAY, {timeout: 2 * 60 * 1000}); 204 | -------------------------------------------------------------------------------- /system-test/test-middleware-express.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* AN EXAMPLE RATHER THAN A TEST AT THIS POINT */ 18 | 19 | import * as assert from 'assert'; 20 | import {describe, it, before} from 'mocha'; 21 | import delay from 'delay'; 22 | import * as uuid from 'uuid'; 23 | import {express as elb} from '../src/index'; 24 | import {Logging} from '@google-cloud/logging'; 25 | 26 | const logging = new Logging(); 27 | 28 | const WRITE_CONSISTENCY_DELAY_MS = 20 * 1000; 29 | const TEST_TIMEOUT = WRITE_CONSISTENCY_DELAY_MS + 10 * 1000; 30 | const LOG_NAME = `bunyan-system-test-${uuid.v4()}`; 31 | 32 | describe('express middleware', () => { 33 | let logger: elb.MiddlewareReturnType['logger']; 34 | let mw: elb.MiddlewareReturnType['mw']; 35 | 36 | before(async () => { 37 | ({logger, mw} = await elb.middleware({logName: LOG_NAME, level: 'info'})); 38 | }); 39 | 40 | describe('global logger', () => { 41 | it('should properly write log entries', async function () { 42 | this.timeout(TEST_TIMEOUT); 43 | const LOG_MESSAGE = `unique log message ${uuid.v4()}`; 44 | logger.info(LOG_MESSAGE); 45 | 46 | await delay(WRITE_CONSISTENCY_DELAY_MS); 47 | 48 | const log = logging.log(`${LOG_NAME}_applog`); 49 | const entries = (await log.getEntries({pageSize: 1}))[0]; 50 | assert.strictEqual(entries.length, 1); 51 | assert.strictEqual(LOG_MESSAGE, entries[0].data.message); 52 | }); 53 | }); 54 | 55 | describe('request logging middleware', () => { 56 | it('should write request correlated log entries', function (done) { 57 | this.timeout(TEST_TIMEOUT); 58 | const LOG_MESSAGE = `correlated log message ${uuid.v4()}`; 59 | const fakeRequest = {headers: {fake: 'header'}}; 60 | const fakeResponse = {}; 61 | const next = async () => { 62 | // At this point fakeRequest.log should have been installed. 63 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 64 | (fakeRequest as any).log.info(LOG_MESSAGE); 65 | 66 | await delay(WRITE_CONSISTENCY_DELAY_MS); 67 | 68 | const log = logging.log(`${LOG_NAME}_applog`); 69 | const entries = (await log.getEntries({pageSize: 1}))[0]; 70 | assert.strictEqual(entries.length, 1); 71 | const entry = entries[0]; 72 | assert.strictEqual(LOG_MESSAGE, entry.data.message); 73 | assert(entry.metadata.trace, 'should have a trace property'); 74 | assert(entry.metadata.trace!.match(/projects\/.*\/traces\/.*/)); 75 | assert(entry.metadata.spanId, 'should have a span property'); 76 | assert(entry.metadata.spanId!.match(/^[0-9]*/)); 77 | assert.strictEqual(entry.metadata.traceSampled, false); 78 | done(); 79 | }; 80 | 81 | // Call middleware with mocks. 82 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 83 | mw(fakeRequest as any, fakeResponse as any, next); 84 | }); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /test/middleware/test-express.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import * as assert from 'assert'; 18 | import {describe, it, beforeEach} from 'mocha'; 19 | import {GCPEnv} from 'google-auth-library'; 20 | import * as proxyquire from 'proxyquire'; 21 | 22 | // types-only import. Actual require is done through proxyquire below. 23 | import {MiddlewareOptions} from '../../src/middleware/express'; 24 | 25 | const FAKE_PROJECT_ID = 'project-🦄'; 26 | const FAKE_GENERATED_MIDDLEWARE = () => {}; 27 | 28 | const FAKE_ENVIRONMENT = 'FAKE_ENVIRONMENT'; 29 | 30 | let authEnvironment: string; 31 | let passedOptions: Array; 32 | 33 | class FakeLoggingBunyan { 34 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 35 | cloudLog: any; 36 | constructor(options: MiddlewareOptions) { 37 | passedOptions.push(options); 38 | this.cloudLog = { 39 | logging: { 40 | auth: { 41 | async getProjectId() { 42 | return FAKE_PROJECT_ID; 43 | }, 44 | async getEnv() { 45 | return authEnvironment; 46 | }, 47 | }, 48 | }, 49 | }; 50 | } 51 | 52 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 53 | stream(level: any) { 54 | return {level, type: 'raw', stream: this}; 55 | } 56 | } 57 | 58 | let passedProjectId: string | undefined; 59 | let passedEmitRequestLog: Function | undefined; 60 | function fakeMakeMiddleware( 61 | projectId: string, 62 | makeChildLogger: Function, 63 | emitRequestLog: Function 64 | ): Function { 65 | passedProjectId = projectId; 66 | passedEmitRequestLog = emitRequestLog; 67 | return FAKE_GENERATED_MIDDLEWARE; 68 | } 69 | 70 | const {middleware, APP_LOG_SUFFIX} = proxyquire( 71 | '../../src/middleware/express', 72 | { 73 | '../../src/index': {LoggingBunyan: FakeLoggingBunyan}, 74 | '@google-cloud/logging': { 75 | middleware: {express: {makeMiddleware: fakeMakeMiddleware}}, 76 | }, 77 | } 78 | ); 79 | 80 | describe('middleware/express', () => { 81 | beforeEach(() => { 82 | passedOptions = []; 83 | passedProjectId = undefined; 84 | passedEmitRequestLog = undefined; 85 | authEnvironment = FAKE_ENVIRONMENT; 86 | }); 87 | 88 | it('should create and return a middleware', async () => { 89 | const {mw} = await middleware(); 90 | assert.strictEqual(mw, FAKE_GENERATED_MIDDLEWARE); 91 | }); 92 | 93 | it('should generate two loggers with default logName and level', async () => { 94 | await middleware(); 95 | // Should generate two loggers with the expected names. 96 | assert.ok(passedOptions); 97 | assert.strictEqual(passedOptions.length, 2); 98 | assert.ok( 99 | passedOptions.some( 100 | option => option!.logName === `bunyan_log_${APP_LOG_SUFFIX}` 101 | ) 102 | ); 103 | assert.ok(passedOptions.some(option => option!.logName === 'bunyan_log')); 104 | assert.ok(passedOptions.every(option => option!.level === 'info')); 105 | }); 106 | 107 | it('should prefer user-provided logName and level', async () => { 108 | const LOGNAME = '㏒'; 109 | const LEVEL = 'fatal'; 110 | const OPTIONS = {logName: LOGNAME, level: LEVEL}; 111 | await middleware(OPTIONS); 112 | assert.ok(passedOptions); 113 | assert.strictEqual(passedOptions.length, 2); 114 | assert.ok( 115 | passedOptions.some( 116 | option => option!.logName === `${LOGNAME}_${APP_LOG_SUFFIX}` 117 | ) 118 | ); 119 | assert.ok(passedOptions.some(option => option!.logName === LOGNAME)); 120 | assert.ok(passedOptions.every(option => option!.level === LEVEL)); 121 | }); 122 | 123 | it('should acquire the projectId and pass to makeMiddleware', async () => { 124 | await middleware(); 125 | assert.strictEqual(passedProjectId, FAKE_PROJECT_ID); 126 | }); 127 | 128 | [GCPEnv.APP_ENGINE, GCPEnv.CLOUD_FUNCTIONS, GCPEnv.CLOUD_RUN].forEach(env => { 129 | it(`should not generate the request logger on ${env}`, async () => { 130 | authEnvironment = env; 131 | if (env === GCPEnv.CLOUD_RUN) { 132 | // Cloud Run needs explicit option flag to enable this behavior until we can make breaking change in next major version 133 | await middleware({skipParentEntryForCloudRun: true}); 134 | } else { 135 | await middleware(); 136 | } 137 | assert.ok(passedOptions); 138 | assert.strictEqual(passedOptions.length, 1); 139 | // emitRequestLog parameter to makeChildLogger should be undefined. 140 | assert.strictEqual(passedEmitRequestLog, undefined); 141 | }); 142 | }); 143 | }); 144 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/gts/tsconfig-google.json", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "outDir": "build", 6 | "lib": ["es2018", "dom"] 7 | }, 8 | "include": [ 9 | "src/*.ts", 10 | "src/**/*.ts", 11 | "test/*.ts", 12 | "test/**/*.ts", 13 | "system-test/*.ts", 14 | "system-test/**/*.ts" 15 | ] 16 | } 17 | --------------------------------------------------------------------------------