From 1080a52e8687f1cbe9716d913b63b0bfa1dfdcf6 Mon Sep 17 00:00:00 2001 From: krisztianfekete Date: Tue, 15 Sep 2026 12:41:18 +0200 Subject: [PATCH 1/3] (otel): vendor OTel logs SDK v0.20.0 --- .../otlp/otlplog/otlploggrpc/LICENSE | 231 +++++ .../go.opentelemetry.io/otel/log/LICENSE | 231 +++++ .../go.opentelemetry.io/otel/sdk/log/LICENSE | 231 +++++ go.mod | 3 + go.sum | 8 + .../otlp/otlplog/otlploggrpc/LICENSE | 231 +++++ .../otlp/otlplog/otlploggrpc/README.md | 3 + .../otlp/otlplog/otlploggrpc/client.go | 296 ++++++ .../otlp/otlplog/otlploggrpc/config.go | 694 ++++++++++++++ .../exporters/otlp/otlplog/otlploggrpc/doc.go | 63 ++ .../otlp/otlplog/otlploggrpc/exporter.go | 94 ++ .../otlp/otlplog/otlploggrpc/internal/gen.go | 20 + .../internal/observ/instrumentation.go | 317 +++++++ .../otlploggrpc/internal/observ/target.go | 143 +++ .../otlploggrpc/internal/partialsuccess.go | 43 + .../otlploggrpc/internal/retry/retry.go | 146 +++ .../otlploggrpc/internal/transform/log.go | 411 +++++++++ .../otlplog/otlploggrpc/internal/version.go | 8 + .../otlplog/otlploggrpc/internal/x/README.md | 36 + .../otlploggrpc/internal/x/features.go | 23 + .../otlp/otlplog/otlploggrpc/internal/x/x.go | 58 ++ .../otlp/otlplog/otlploggrpc/version.go | 9 + vendor/go.opentelemetry.io/otel/log/DESIGN.md | 634 +++++++++++++ vendor/go.opentelemetry.io/otel/log/LICENSE | 231 +++++ vendor/go.opentelemetry.io/otel/log/README.md | 3 + vendor/go.opentelemetry.io/otel/log/doc.go | 85 ++ .../otel/log/embedded/README.md | 3 + .../otel/log/embedded/embedded.go | 36 + .../otel/log/global/README.md | 3 + .../otel/log/global/log.go | 49 + .../otel/log/internal/global/log.go | 109 +++ .../otel/log/internal/global/state.go | 53 ++ .../go.opentelemetry.io/otel/log/keyvalue.go | 453 +++++++++ .../otel/log/kind_string.go | 31 + vendor/go.opentelemetry.io/otel/log/logger.go | 188 ++++ .../otel/log/noop/README.md | 3 + .../go.opentelemetry.io/otel/log/noop/noop.go | 50 + .../go.opentelemetry.io/otel/log/provider.go | 37 + vendor/go.opentelemetry.io/otel/log/record.go | 163 ++++ .../go.opentelemetry.io/otel/log/severity.go | 64 ++ .../otel/log/severity_string.go | 48 + .../otel/sdk/log/DESIGN.md | 176 ++++ .../go.opentelemetry.io/otel/sdk/log/LICENSE | 231 +++++ .../otel/sdk/log/README.md | 3 + .../go.opentelemetry.io/otel/sdk/log/batch.go | 496 ++++++++++ .../go.opentelemetry.io/otel/sdk/log/doc.go | 39 + .../otel/sdk/log/exporter.go | 326 +++++++ .../otel/sdk/log/instrumentation.go | 39 + .../otel/sdk/log/internal/observ/doc.go | 6 + .../internal/observ/simple_log_processor.go | 129 +++ .../otel/sdk/log/internal/x/README.md | 34 + .../otel/sdk/log/internal/x/features.go | 23 + .../otel/sdk/log/internal/x/x.go | 58 ++ .../otel/sdk/log/logger.go | 200 ++++ .../otel/sdk/log/processor.go | 96 ++ .../otel/sdk/log/provider.go | 276 ++++++ .../otel/sdk/log/record.go | 756 +++++++++++++++ .../go.opentelemetry.io/otel/sdk/log/ring.go | 82 ++ .../otel/sdk/log/setting.go | 119 +++ .../otel/sdk/log/simple.go | 104 +++ .../otlp/collector/logs/v1/logs_service.pb.go | 367 ++++++++ .../collector/logs/v1/logs_service.pb.gw.go | 171 ++++ .../collector/logs/v1/logs_service_grpc.pb.go | 105 +++ .../proto/otlp/logs/v1/logs.pb.go | 870 ++++++++++++++++++ vendor/modules.txt | 22 + 65 files changed, 10270 insertions(+) create mode 100644 _LICENSES/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/LICENSE create mode 100644 _LICENSES/go.opentelemetry.io/otel/log/LICENSE create mode 100644 _LICENSES/go.opentelemetry.io/otel/sdk/log/LICENSE create mode 100644 vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/LICENSE create mode 100644 vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/README.md create mode 100644 vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/client.go create mode 100644 vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/config.go create mode 100644 vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/doc.go create mode 100644 vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/exporter.go create mode 100644 vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/gen.go create mode 100644 vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/observ/instrumentation.go create mode 100644 vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/observ/target.go create mode 100644 vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/partialsuccess.go create mode 100644 vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/retry/retry.go create mode 100644 vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/transform/log.go create mode 100644 vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/version.go create mode 100644 vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/x/README.md create mode 100644 vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/x/features.go create mode 100644 vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/x/x.go create mode 100644 vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/version.go create mode 100644 vendor/go.opentelemetry.io/otel/log/DESIGN.md create mode 100644 vendor/go.opentelemetry.io/otel/log/LICENSE create mode 100644 vendor/go.opentelemetry.io/otel/log/README.md create mode 100644 vendor/go.opentelemetry.io/otel/log/doc.go create mode 100644 vendor/go.opentelemetry.io/otel/log/embedded/README.md create mode 100644 vendor/go.opentelemetry.io/otel/log/embedded/embedded.go create mode 100644 vendor/go.opentelemetry.io/otel/log/global/README.md create mode 100644 vendor/go.opentelemetry.io/otel/log/global/log.go create mode 100644 vendor/go.opentelemetry.io/otel/log/internal/global/log.go create mode 100644 vendor/go.opentelemetry.io/otel/log/internal/global/state.go create mode 100644 vendor/go.opentelemetry.io/otel/log/keyvalue.go create mode 100644 vendor/go.opentelemetry.io/otel/log/kind_string.go create mode 100644 vendor/go.opentelemetry.io/otel/log/logger.go create mode 100644 vendor/go.opentelemetry.io/otel/log/noop/README.md create mode 100644 vendor/go.opentelemetry.io/otel/log/noop/noop.go create mode 100644 vendor/go.opentelemetry.io/otel/log/provider.go create mode 100644 vendor/go.opentelemetry.io/otel/log/record.go create mode 100644 vendor/go.opentelemetry.io/otel/log/severity.go create mode 100644 vendor/go.opentelemetry.io/otel/log/severity_string.go create mode 100644 vendor/go.opentelemetry.io/otel/sdk/log/DESIGN.md create mode 100644 vendor/go.opentelemetry.io/otel/sdk/log/LICENSE create mode 100644 vendor/go.opentelemetry.io/otel/sdk/log/README.md create mode 100644 vendor/go.opentelemetry.io/otel/sdk/log/batch.go create mode 100644 vendor/go.opentelemetry.io/otel/sdk/log/doc.go create mode 100644 vendor/go.opentelemetry.io/otel/sdk/log/exporter.go create mode 100644 vendor/go.opentelemetry.io/otel/sdk/log/instrumentation.go create mode 100644 vendor/go.opentelemetry.io/otel/sdk/log/internal/observ/doc.go create mode 100644 vendor/go.opentelemetry.io/otel/sdk/log/internal/observ/simple_log_processor.go create mode 100644 vendor/go.opentelemetry.io/otel/sdk/log/internal/x/README.md create mode 100644 vendor/go.opentelemetry.io/otel/sdk/log/internal/x/features.go create mode 100644 vendor/go.opentelemetry.io/otel/sdk/log/internal/x/x.go create mode 100644 vendor/go.opentelemetry.io/otel/sdk/log/logger.go create mode 100644 vendor/go.opentelemetry.io/otel/sdk/log/processor.go create mode 100644 vendor/go.opentelemetry.io/otel/sdk/log/provider.go create mode 100644 vendor/go.opentelemetry.io/otel/sdk/log/record.go create mode 100644 vendor/go.opentelemetry.io/otel/sdk/log/ring.go create mode 100644 vendor/go.opentelemetry.io/otel/sdk/log/setting.go create mode 100644 vendor/go.opentelemetry.io/otel/sdk/log/simple.go create mode 100644 vendor/go.opentelemetry.io/proto/otlp/collector/logs/v1/logs_service.pb.go create mode 100644 vendor/go.opentelemetry.io/proto/otlp/collector/logs/v1/logs_service.pb.gw.go create mode 100644 vendor/go.opentelemetry.io/proto/otlp/collector/logs/v1/logs_service_grpc.pb.go create mode 100644 vendor/go.opentelemetry.io/proto/otlp/logs/v1/logs.pb.go diff --git a/_LICENSES/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/LICENSE b/_LICENSES/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/LICENSE new file mode 100644 index 0000000000..f1aee0f110 --- /dev/null +++ b/_LICENSES/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/LICENSE @@ -0,0 +1,231 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +-------------------------------------------------------------------------------- + +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/_LICENSES/go.opentelemetry.io/otel/log/LICENSE b/_LICENSES/go.opentelemetry.io/otel/log/LICENSE new file mode 100644 index 0000000000..f1aee0f110 --- /dev/null +++ b/_LICENSES/go.opentelemetry.io/otel/log/LICENSE @@ -0,0 +1,231 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +-------------------------------------------------------------------------------- + +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/_LICENSES/go.opentelemetry.io/otel/sdk/log/LICENSE b/_LICENSES/go.opentelemetry.io/otel/sdk/log/LICENSE new file mode 100644 index 0000000000..f1aee0f110 --- /dev/null +++ b/_LICENSES/go.opentelemetry.io/otel/sdk/log/LICENSE @@ -0,0 +1,231 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +-------------------------------------------------------------------------------- + +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/go.mod b/go.mod index 4edf49121d..3fa0dd0198 100644 --- a/go.mod +++ b/go.mod @@ -46,11 +46,14 @@ require ( go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 go.opentelemetry.io/otel v1.44.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.20.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 go.opentelemetry.io/otel/exporters/prometheus v0.65.0 + go.opentelemetry.io/otel/log v0.20.0 go.opentelemetry.io/otel/metric v1.44.0 go.opentelemetry.io/otel/sdk v1.44.0 + go.opentelemetry.io/otel/sdk/log v0.20.0 go.opentelemetry.io/otel/sdk/metric v1.44.0 go.opentelemetry.io/otel/trace v1.44.0 go.opentelemetry.io/proto/otlp v1.11.0 diff --git a/go.sum b/go.sum index c3824e7fbd..0fdd481314 100644 --- a/go.sum +++ b/go.sum @@ -415,6 +415,8 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.20.0 h1:rydZ9sxbcFdm/oWrVyfLTjHIygMgv0bEeMd+3B/BvoM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.20.0/go.mod h1:earQ25dooT0Hhspq59DZ8YCC50jWfOlFEeWoxy/P444= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 h1:8UQVDcZxOJLtX6gxtDt3vY2WTgvZqMQRzjsqiIHQdkc= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0/go.mod h1:2lmweYCiHYpEjQ/lSJBYhj9jP1zvCvQW4BqL9dnT7FQ= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= @@ -425,12 +427,18 @@ go.opentelemetry.io/otel/exporters/prometheus v0.65.0 h1:jOveH/b4lU9HT7y+Gfamf18 go.opentelemetry.io/otel/exporters/prometheus v0.65.0/go.mod h1:i1P8pcumauPtUI4YNopea1dhzEMuEqWP1xoUZDylLHo= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0 h1:TC+BewnDpeiAmcscXbGMfxkO+mwYUwE/VySwvw88PfA= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0/go.mod h1:J/ZyF4vfPwsSr9xJSPyQ4LqtcTPULFR64KwTikGLe+A= +go.opentelemetry.io/otel/log v0.20.0 h1:/5i0vuHxCLWUfChWG41K9wkM0jafruPw9NU1/RCJirs= +go.opentelemetry.io/otel/log v0.20.0/go.mod h1:wOcMcjsZpG8x7Bak7IhSi/lg8wscV2C1VdrKCLPlt0E= go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA= go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk= go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/sdk/log v0.20.0 h1:vM3xI7TQgKPiSghe6urZtAkyFY7SodrSpC83CffDFuY= +go.opentelemetry.io/otel/sdk/log v0.20.0/go.mod h1:Knej2nmsTUzN79T2eeXdRsjjPcoxoq2pUyUHz9TFyyU= +go.opentelemetry.io/otel/sdk/log/logtest v0.20.0 h1:OqdRZ1guyzamK3M6LlRsmGqRrjkHWw6WZOKKli5ELpg= +go.opentelemetry.io/otel/sdk/log/logtest v0.20.0/go.mod h1:PuMIlm7zAt7c3z8zfOI5ox4iT1Z87We+PF6YoINux/M= go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/LICENSE b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/LICENSE new file mode 100644 index 0000000000..f1aee0f110 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/LICENSE @@ -0,0 +1,231 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +-------------------------------------------------------------------------------- + +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/README.md b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/README.md new file mode 100644 index 0000000000..0b4603c8e4 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/README.md @@ -0,0 +1,3 @@ +# OTLP Log gRPC Exporter + +[![PkgGoDev](https://pkg.go.dev/badge/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc)](https://pkg.go.dev/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc) diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/client.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/client.go new file mode 100644 index 0000000000..5488a4c626 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/client.go @@ -0,0 +1,296 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package otlploggrpc // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc" + +import ( + "context" + "errors" + "fmt" + "sync/atomic" + "time" + + collogpb "go.opentelemetry.io/proto/otlp/collector/logs/v1" + logpb "go.opentelemetry.io/proto/otlp/logs/v1" + "google.golang.org/genproto/googleapis/rpc/errdetails" + "google.golang.org/grpc" + "google.golang.org/grpc/backoff" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/encoding/gzip" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + + "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal" + "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/observ" + "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/retry" +) + +// The methods of this type are not expected to be called concurrently. +type client struct { + metadata metadata.MD + exportTimeout time.Duration + maxRequestSize int + requestFunc retry.RequestFunc + + // ourConn keeps track of where conn was created: true if created here in + // NewClient, or false if passed with an option. This is important on + // Shutdown as conn should only be closed if we created it. Otherwise, + // it is up to the processes that passed conn to close it. + ourConn bool + conn *grpc.ClientConn + lsc collogpb.LogsServiceClient + + instrumentation *observ.Instrumentation +} + +// Used for testing. +var newGRPCClientFn = grpc.NewClient + +// newClient creates a new gRPC log client. +func newClient(cfg config) (*client, error) { + c := &client{ + exportTimeout: cfg.timeout.Value, + maxRequestSize: cfg.maxRequestSize.Value, + requestFunc: cfg.retryCfg.Value.RequestFunc(retryable), + conn: cfg.gRPCConn.Value, + } + + if len(cfg.headers.Value) > 0 { + c.metadata = metadata.New(cfg.headers.Value) + } + + if c.conn == nil { + // If the caller did not provide a ClientConn when the client was + // created, create one using the configuration they did provide. + dialOpts := newGRPCDialOptions(cfg) + + conn, err := newGRPCClientFn(cfg.endpoint.Value, dialOpts...) + if err != nil { + return nil, err + } + // Keep track that we own the lifecycle of this conn and need to close + // it on Shutdown. + c.ourConn = true + c.conn = conn + } + + c.lsc = collogpb.NewLogsServiceClient(c.conn) + + var err error + id := nextExporterID() + c.instrumentation, err = observ.NewInstrumentation(id, c.conn.CanonicalTarget()) + return c, err +} + +var exporterN atomic.Int64 + +// nextExporterID returns the next unique ID for an exporter. +func nextExporterID() int64 { + const inc = 1 + return exporterN.Add(inc) - inc +} + +func newGRPCDialOptions(cfg config) []grpc.DialOption { + userAgent := "OTel Go OTLP over gRPC logs exporter/" + Version() + dialOpts := []grpc.DialOption{grpc.WithUserAgent(userAgent)} + dialOpts = append(dialOpts, cfg.dialOptions.Value...) + + // Convert other grpc configs to the dial options. + // Service config + if cfg.serviceConfig.Value != "" { + dialOpts = append(dialOpts, grpc.WithDefaultServiceConfig(cfg.serviceConfig.Value)) + } + // Prioritize GRPCCredentials over Insecure (passing both is an error). + switch { + case cfg.gRPCCredentials.Value != nil: + dialOpts = append(dialOpts, grpc.WithTransportCredentials(cfg.gRPCCredentials.Value)) + case cfg.insecure.Value: + dialOpts = append(dialOpts, grpc.WithTransportCredentials(insecure.NewCredentials())) + default: + // Default to using the host's root CA. + dialOpts = append(dialOpts, grpc.WithTransportCredentials( + credentials.NewTLS(nil), + )) + } + // Compression + if cfg.compression.Value == GzipCompression { + dialOpts = append(dialOpts, grpc.WithDefaultCallOptions(grpc.UseCompressor(gzip.Name))) + } + // Reconnection period + if cfg.reconnectionPeriod.Value != 0 { + p := grpc.ConnectParams{ + Backoff: backoff.DefaultConfig, + MinConnectTimeout: cfg.reconnectionPeriod.Value, + } + dialOpts = append(dialOpts, grpc.WithConnectParams(p)) + } + + return dialOpts +} + +// UploadLogs sends proto logs to connected endpoint. +// +// Retryable errors from the server will be handled according to any +// RetryConfig the client was created with. +// +// The otlplog.Exporter synchronizes access to client methods, and +// ensures this is not called after the Exporter is shutdown. Only thing +// to do here is send data. +func (c *client) UploadLogs(ctx context.Context, rl []*logpb.ResourceLogs) (uploadErr error) { + select { + case <-ctx.Done(): + // Do not upload if the context is already expired. + return ctx.Err() + default: + } + + ctx, cancel := c.exportContext(ctx) + defer cancel() + + pbRequest := &collogpb.ExportLogsServiceRequest{ResourceLogs: rl} + if c.instrumentation != nil { + var count int64 + for _, resLogs := range rl { + for _, scopeLogs := range resLogs.ScopeLogs { + count += int64(len(scopeLogs.LogRecords)) + } + } + eo := c.instrumentation.ExportLogs(ctx, count) + defer func() { + eo.End(uploadErr) + }() + } + + if maxSize := c.maxRequestSize; maxSize > 0 && proto.Size(pbRequest) > maxSize { + return fmt.Errorf("request message too large: exceeded %d bytes", maxSize) + } + + return errors.Join(uploadErr, c.requestFunc(ctx, func(ctx context.Context) error { + resp, err := c.lsc.Export(ctx, pbRequest) + if resp != nil && resp.PartialSuccess != nil { + msg := resp.PartialSuccess.GetErrorMessage() + n := resp.PartialSuccess.GetRejectedLogRecords() + if n != 0 || msg != "" { + err := internal.LogPartialSuccessError(n, msg) + uploadErr = errors.Join(uploadErr, err) + } + } + // nil is converted to OK. + if status.Code(err) == codes.OK { + // Success. + return nil + } + return err + })) +} + +// Shutdown shuts down the client, freeing all resources. +// +// Any active connections to a remote endpoint are closed if they were created +// by the client. Any gRPC connection passed during creation using +// WithGRPCConn will not be closed. It is the caller's responsibility to +// handle cleanup of that resource. +// +// The otlplog.Exporter synchronizes access to client methods and +// ensures this is called only once. The only thing that needs to be done +// here is to release any computational resources the client holds. +func (c *client) Shutdown(ctx context.Context) error { + c.metadata = nil + c.requestFunc = nil + c.lsc = nil + + // Release the connection if we created it. + err := ctx.Err() + if c.ourConn { + closeErr := c.conn.Close() + // A context timeout error takes precedence over this error. + if err == nil && closeErr != nil { + err = closeErr + } + } + c.conn = nil + return err +} + +// exportContext returns a copy of parent with an appropriate deadline and +// cancellation function based on the clients configured export timeout. +// +// It is the callers responsibility to cancel the returned context once its +// use is complete, via the parent or directly with the returned CancelFunc, to +// ensure all resources are correctly released. +func (c *client) exportContext(parent context.Context) (context.Context, context.CancelFunc) { + var ( + ctx context.Context + cancel context.CancelFunc + ) + + if c.exportTimeout > 0 { + ctx, cancel = context.WithTimeoutCause(parent, c.exportTimeout, errors.New("exporter export timeout")) + } else { + ctx, cancel = context.WithCancel(parent) //nolint:gosec // cancel is handled by caller. + } + + if c.metadata.Len() > 0 { + md := c.metadata + if outMD, ok := metadata.FromOutgoingContext(ctx); ok { + md = metadata.Join(md, outMD) + } + + ctx = metadata.NewOutgoingContext(ctx, md) + } + + return ctx, cancel +} + +type noopClient struct{} + +func newNoopClient() *noopClient { + return &noopClient{} +} + +func (*noopClient) UploadLogs(context.Context, []*logpb.ResourceLogs) error { return nil } + +func (*noopClient) Shutdown(context.Context) error { return nil } + +// retryable returns if err identifies a request that can be retried and a +// duration to wait for if an explicit throttle time is included in err. +func retryable(err error) (bool, time.Duration) { + s := status.Convert(err) + return retryableGRPCStatus(s) +} + +func retryableGRPCStatus(s *status.Status) (bool, time.Duration) { + switch s.Code() { + // Follows the retryable error codes defined in + // https://opentelemetry.io/docs/specs/otlp/#failures + case codes.Canceled, + codes.DeadlineExceeded, + codes.Aborted, + codes.OutOfRange, + codes.Unavailable, + codes.DataLoss: + // Additionally, handle RetryInfo. + _, d := throttleDelay(s) + return true, d + case codes.ResourceExhausted: + // Retry only if the server signals that the recovery from resource exhaustion is possible. + return throttleDelay(s) + } + + // Not a retry-able error. + return false, 0 +} + +// throttleDelay returns if the status is RetryInfo +// and the duration to wait for if an explicit throttle time is included. +func throttleDelay(s *status.Status) (bool, time.Duration) { + for _, detail := range s.Details() { + if t, ok := detail.(*errdetails.RetryInfo); ok { + return true, t.RetryDelay.AsDuration() + } + } + return false, 0 +} diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/config.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/config.go new file mode 100644 index 0000000000..6f8a0f9d1f --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/config.go @@ -0,0 +1,694 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package otlploggrpc // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc" + +import ( + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "net/url" + "os" + "strconv" + "strings" + "time" + "unicode" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/retry" + "go.opentelemetry.io/otel/internal/global" +) + +// Default values. +var ( + defaultEndpoint = "localhost:4317" + defaultTimeout = 10 * time.Second + defaultMaxRequestSize = 64 * 1024 * 1024 + defaultRetryCfg = retry.DefaultConfig +) + +// Environment variable keys. +var ( + envEndpoint = []string{ + "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT", + "OTEL_EXPORTER_OTLP_ENDPOINT", + } + envInsecure = []string{ + "OTEL_EXPORTER_OTLP_LOGS_INSECURE", + "OTEL_EXPORTER_OTLP_INSECURE", + } + + envHeaders = []string{ + "OTEL_EXPORTER_OTLP_LOGS_HEADERS", + "OTEL_EXPORTER_OTLP_HEADERS", + } + + envCompression = []string{ + "OTEL_EXPORTER_OTLP_LOGS_COMPRESSION", + "OTEL_EXPORTER_OTLP_COMPRESSION", + } + + envTimeout = []string{ + "OTEL_EXPORTER_OTLP_LOGS_TIMEOUT", + "OTEL_EXPORTER_OTLP_TIMEOUT", + } + + envTLSCert = []string{ + "OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE", + "OTEL_EXPORTER_OTLP_CERTIFICATE", + } + envTLSClient = []struct { + Certificate string + Key string + }{ + { + "OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE", + "OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY", + }, + { + "OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE", + "OTEL_EXPORTER_OTLP_CLIENT_KEY", + }, + } +) + +type fnOpt func(config) config + +func (f fnOpt) applyOption(c config) config { return f(c) } + +// Option applies an option to the Exporter. +type Option interface { + applyOption(config) config +} + +type config struct { + endpoint setting[string] + insecure setting[bool] + tlsCfg setting[*tls.Config] + headers setting[map[string]string] + compression setting[Compression] + maxRequestSize setting[int] + timeout setting[time.Duration] + retryCfg setting[retry.Config] + + // gRPC configurations + gRPCCredentials setting[credentials.TransportCredentials] + serviceConfig setting[string] + reconnectionPeriod setting[time.Duration] + dialOptions setting[[]grpc.DialOption] + gRPCConn setting[*grpc.ClientConn] +} + +func newConfig(options []Option) config { + var c config + for _, opt := range options { + c = opt.applyOption(c) + } + + // Apply environment value and default value + c.endpoint = c.endpoint.Resolve( + getEnv[string](envEndpoint, convEndpoint), + fallback[string](defaultEndpoint), + ) + c.insecure = c.insecure.Resolve( + loadInsecureFromEnvEndpoint(envEndpoint), + getEnv[bool](envInsecure, convInsecure), + ) + c.tlsCfg = c.tlsCfg.Resolve( + loadEnvTLS[*tls.Config](), + ) + c.headers = c.headers.Resolve( + getEnv[map[string]string](envHeaders, convHeaders), + ) + c.compression = c.compression.Resolve( + getEnv[Compression](envCompression, convCompression), + ) + c.timeout = c.timeout.Resolve( + getEnv[time.Duration](envTimeout, convDuration), + fallback[time.Duration](defaultTimeout), + ) + c.maxRequestSize = c.maxRequestSize.Resolve( + fallback[int](defaultMaxRequestSize), + ) + c.retryCfg = c.retryCfg.Resolve( + fallback[retry.Config](defaultRetryCfg), + ) + + return c +} + +// RetryConfig defines configuration for retrying the export of log data +// that failed. +// +// This configuration does not define any network retry strategy. That is +// entirely handled by the gRPC ClientConn. +type RetryConfig retry.Config + +// WithInsecure disables client transport security for the Exporter's gRPC +// connection, just like grpc.WithInsecure() +// (https://pkg.go.dev/google.golang.org/grpc#WithInsecure) does. +// +// If the OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_LOGS_ENDPOINT +// environment variable is set, and this option is not passed, that variable +// value will be used to determine client security. If the endpoint has a +// scheme of "http" or "unix" client security will be disabled. If both are +// set, OTEL_EXPORTER_OTLP_LOGS_ENDPOINT will take precedence. +// +// By default, if an environment variable is not set, and this option is not +// passed, client security will be used. +// +// This option has no effect if WithGRPCConn is used. +func WithInsecure() Option { + return fnOpt(func(c config) config { + c.insecure = newSetting(true) + return c + }) +} + +// WithEndpoint sets the target endpoint the Exporter will connect to. +// +// If the OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_LOGS_ENDPOINT +// environment variable is set, and this option is not passed, that variable +// value will be used. If both are set, OTEL_EXPORTER_OTLP_LOGS_ENDPOINT +// will take precedence. +// +// If both this option and WithEndpointURL are used, the last used option will +// take precedence. +// +// By default, if an environment variable is not set, and this option is not +// passed, "localhost:4317" will be used. +// +// This option has no effect if WithGRPCConn is used. +func WithEndpoint(endpoint string) Option { + return fnOpt(func(c config) config { + c.endpoint = newSetting(endpoint) + return c + }) +} + +// WithEndpointURL sets the target endpoint URL the Exporter will connect to. +// +// If the OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_LOGS_ENDPOINT +// environment variable is set, and this option is not passed, that variable +// value will be used. If both are set, OTEL_EXPORTER_OTLP_LOGS_ENDPOINT +// will take precedence. +// +// If both this option and WithEndpoint are used, the last used option will +// take precedence. +// +// If an invalid URL is provided, the default value will be kept. +// +// By default, if an environment variable is not set, and this option is not +// passed, "localhost:4317" will be used. +// +// This option has no effect if WithGRPCConn is used. +func WithEndpointURL(rawURL string) Option { + u, err := url.Parse(rawURL) + if err != nil { + global.Error(err, "otlplog: parse endpoint url", "url", rawURL) + return fnOpt(func(c config) config { return c }) + } + return fnOpt(func(c config) config { + c.endpoint = newSetting(u.Host) + c.insecure = insecureFromScheme(c.insecure, u.Scheme) + return c + }) +} + +// WithReconnectionPeriod set the minimum amount of time between connection +// attempts to the target endpoint. +// +// This option has no effect if WithGRPCConn is used. +func WithReconnectionPeriod(rp time.Duration) Option { + return fnOpt(func(c config) config { + c.reconnectionPeriod = newSetting(rp) + return c + }) +} + +// Compression describes the compression used for exported payloads. +type Compression int + +const ( + // NoCompression represents that no compression should be used. + NoCompression Compression = iota + // GzipCompression represents that gzip compression should be used. + GzipCompression +) + +// WithCompressor sets the compressor the gRPC client uses. +// Supported compressor values: "gzip". +// +// If the OTEL_EXPORTER_OTLP_COMPRESSION or +// OTEL_EXPORTER_OTLP_LOGS_COMPRESSION environment variable is set, and +// this option is not passed, that variable value will be used. That value can +// be either "none" or "gzip". If both are set, +// OTEL_EXPORTER_OTLP_LOGS_COMPRESSION will take precedence. +// +// By default, if an environment variable is not set, and this option is not +// passed, no compression strategy will be used. +// +// This option has no effect if WithGRPCConn is used. +func WithCompressor(compressor string) Option { + return fnOpt(func(c config) config { + c.compression = newSetting(compressorToCompression(compressor)) + return c + }) +} + +// WithHeaders will send the provided headers with each gRPC requests. +// +// If the OTEL_EXPORTER_OTLP_HEADERS or OTEL_EXPORTER_OTLP_LOGS_HEADERS +// environment variable is set, and this option is not passed, that variable +// value will be used. The value will be parsed as a list of key value pairs. +// These pairs are expected to be in the W3C Correlation-Context format +// without additional semi-colon delimited metadata (i.e. "k1=v1,k2=v2"). If +// both are set, OTEL_EXPORTER_OTLP_LOGS_HEADERS will take precedence. +// +// By default, if an environment variable is not set, and this option is not +// passed, no user headers will be set. +func WithHeaders(headers map[string]string) Option { + return fnOpt(func(c config) config { + c.headers = newSetting(headers) + return c + }) +} + +// WithTLSCredentials sets the gRPC connection to use creds. +// +// If the OTEL_EXPORTER_OTLP_CERTIFICATE or +// OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE environment variable is set, and +// this option is not passed, that variable value will be used. The value will +// be parsed the filepath of the TLS certificate chain to use. If both are +// set, OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE will take precedence. +// +// By default, if an environment variable is not set, and this option is not +// passed, no TLS credentials will be used. +// +// This option has no effect if WithGRPCConn is used. +func WithTLSCredentials(credential credentials.TransportCredentials) Option { + return fnOpt(func(c config) config { + c.gRPCCredentials = newSetting(credential) + return c + }) +} + +// WithServiceConfig defines the default gRPC service config used. +// +// This option has no effect if WithGRPCConn is used. +func WithServiceConfig(serviceConfig string) Option { + return fnOpt(func(c config) config { + c.serviceConfig = newSetting(serviceConfig) + return c + }) +} + +// WithDialOption sets explicit grpc.DialOptions to use when establishing a +// gRPC connection. The options here are appended to the internal grpc.DialOptions +// used so they will take precedence over any other internal grpc.DialOptions +// they might conflict with. +// The [grpc.WithBlock], [grpc.WithTimeout], and [grpc.WithReturnConnectionError] +// grpc.DialOptions are ignored. +// +// This option has no effect if WithGRPCConn is used. +func WithDialOption(opts ...grpc.DialOption) Option { + return fnOpt(func(c config) config { + c.dialOptions = newSetting(opts) + return c + }) +} + +// WithGRPCConn sets conn as the gRPC ClientConn used for all communication. +// +// This option takes precedence over any other option that relates to +// establishing or persisting a gRPC connection to a target endpoint. Any +// other option of those types passed will be ignored. +// +// It is the callers responsibility to close the passed conn. The Exporter +// Shutdown method will not close this connection. +func WithGRPCConn(conn *grpc.ClientConn) Option { + return fnOpt(func(c config) config { + c.gRPCConn = newSetting(conn) + return c + }) +} + +// WithTimeout sets the max amount of time an Exporter will attempt an export. +// +// This takes precedence over any retry settings defined by WithRetry. Once +// this time limit has been reached the export is abandoned and the log +// data is dropped. +// +// If the OTEL_EXPORTER_OTLP_TIMEOUT or OTEL_EXPORTER_OTLP_LOGS_TIMEOUT +// environment variable is set, and this option is not passed, that variable +// value will be used. The value will be parsed as an integer representing the +// timeout in milliseconds. If both are set, +// OTEL_EXPORTER_OTLP_LOGS_TIMEOUT will take precedence. +// +// By default, if an environment variable is not set, and this option is not +// passed, a timeout of 10 seconds will be used. +func WithTimeout(duration time.Duration) Option { + return fnOpt(func(c config) config { + c.timeout = newSetting(duration) + return c + }) +} + +// WithMaxRequestSize sets the maximum size, in bytes, of a serialized export +// request, before compression, that the exporter will send. +// +// If size is less than or equal to zero, no request-size limit is applied. +// Disabling the limit is not recommended because it can lead to excessive +// resource consumption or abuse. +func WithMaxRequestSize(size int) Option { + return fnOpt(func(c config) config { + c.maxRequestSize = newSetting(size) + return c + }) +} + +// WithRetry sets the retry policy for transient retryable errors that are +// returned by the target endpoint. +// +// If the target endpoint responds with not only a retryable error, but +// explicitly returns a backoff time in the response, that time will take +// precedence over these settings. +// +// These settings define the retry strategy implemented by the exporter. +// These settings do not define any network retry strategy. +// That is handled by the gRPC ClientConn. +// +// If unset, the default retry policy will be used. It will retry the export +// 5 seconds after receiving a retryable error and increase exponentially +// after each error for no more than a total time of 1 minute. +func WithRetry(rc RetryConfig) Option { + return fnOpt(func(c config) config { + c.retryCfg = newSetting(retry.Config(rc)) + return c + }) +} + +// convCompression returns the parsed compression encoded in s. NoCompression +// and an errors are returned if s is unknown. +func convCompression(s string) (Compression, error) { + switch s { + case "gzip": + return GzipCompression, nil + case "none", "": + return NoCompression, nil + } + return NoCompression, fmt.Errorf("unknown compression: %s", s) +} + +// convEndpoint converts s from a URL string to an endpoint if s is a valid +// URL. Otherwise, "" and an error are returned. +func convEndpoint(s string) (string, error) { + u, err := url.Parse(s) + if err != nil { + return "", err + } + return u.Host, nil +} + +// convInsecure converts s from string to bool without case sensitivity. +// If s is not valid returns error. +func convInsecure(s string) (bool, error) { + s = strings.ToLower(s) + if s != "true" && s != "false" { + return false, fmt.Errorf("can't convert %q to bool", s) + } + + return s == "true", nil +} + +// loadInsecureFromEnvEndpoint returns a resolver that fetches +// insecure setting from envEndpoint is it possible. +func loadInsecureFromEnvEndpoint(envEndpoint []string) resolver[bool] { + return func(s setting[bool]) setting[bool] { + if s.Set { + // Passed, valid, options have precedence. + return s + } + + for _, key := range envEndpoint { + if vStr := os.Getenv(key); vStr != "" { + u, err := url.Parse(vStr) + if err != nil { + otel.Handle(fmt.Errorf("invalid %s value %s: %w", key, vStr, err)) + continue + } + + return insecureFromScheme(s, u.Scheme) + } + } + return s + } +} + +// convHeaders converts the OTel environment variable header value s into a +// mapping of header key to value. If s is invalid a partial result and error +// are returned. +func convHeaders(s string) (map[string]string, error) { + out := make(map[string]string) + var err error + for header := range strings.SplitSeq(s, ",") { + rawKey, rawVal, found := strings.Cut(header, "=") + if !found { + err = errors.Join(err, fmt.Errorf("invalid header: %s", header)) + continue + } + + key := strings.TrimSpace(rawKey) + + // Validate the key. + if !isValidHeaderKey(key) { + err = errors.Join(err, fmt.Errorf("invalid header key: %s", rawKey)) + continue + } + + // Only decode the value. + escVal, e := url.PathUnescape(rawVal) + if e != nil { + err = errors.Join(err, fmt.Errorf("invalid header value: %s", rawVal)) + continue + } + val := strings.TrimSpace(escVal) + + out[key] = val + } + return out, err +} + +// convDuration converts s into a duration of milliseconds. If s does not +// contain an integer, 0 and an error are returned. +func convDuration(s string) (time.Duration, error) { + d, err := strconv.Atoi(s) + if err != nil { + return 0, err + } + // OTel durations are defined in milliseconds. + return time.Duration(d) * time.Millisecond, nil +} + +// loadEnvTLS returns a resolver that loads a *tls.Config from files defined by +// the OTLP TLS environment variables. This will load both the rootCAs and +// certificates used for mTLS. +// +// If the filepath defined is invalid or does not contain valid TLS files, an +// error is passed to the OTel ErrorHandler and no TLS configuration is +// provided. +func loadEnvTLS[T *tls.Config]() resolver[T] { + return func(s setting[T]) setting[T] { + if s.Set { + // Passed, valid, options have precedence. + return s + } + + var rootCAs *x509.CertPool + var err error + for _, key := range envTLSCert { + if v := os.Getenv(key); v != "" { + rootCAs, err = loadCertPool(v) + break + } + } + + var certs []tls.Certificate + for _, pair := range envTLSClient { + cert := os.Getenv(pair.Certificate) + key := os.Getenv(pair.Key) + if cert != "" && key != "" { + var e error + certs, e = loadCertificates(cert, key) + err = errors.Join(err, e) + break + } + } + + if err != nil { + err = fmt.Errorf("failed to load TLS: %w", err) + otel.Handle(err) + } else if rootCAs != nil || certs != nil { + s.Set = true + s.Value = &tls.Config{RootCAs: rootCAs, Certificates: certs} + } + return s + } +} + +// readFile is used for testing. +var readFile = os.ReadFile + +// loadCertPool loads and returns the *x509.CertPool found at path if it exists +// and is valid. Otherwise, nil and an error is returned. +func loadCertPool(path string) (*x509.CertPool, error) { + b, err := readFile(path) + if err != nil { + return nil, err + } + cp := x509.NewCertPool() + if ok := cp.AppendCertsFromPEM(b); !ok { + return nil, errors.New("certificate not added") + } + return cp, nil +} + +// loadCertificates loads and returns the tls.Certificate found at path if it +// exists and is valid. Otherwise, nil and an error is returned. +func loadCertificates(certPath, keyPath string) ([]tls.Certificate, error) { + cert, err := readFile(certPath) + if err != nil { + return nil, err + } + key, err := readFile(keyPath) + if err != nil { + return nil, err + } + crt, err := tls.X509KeyPair(cert, key) + if err != nil { + return nil, err + } + return []tls.Certificate{crt}, nil +} + +// insecureFromScheme return setting if the connection should +// use client transport security or not. +// Empty scheme doesn't force insecure setting. +func insecureFromScheme(prev setting[bool], scheme string) setting[bool] { + if scheme == "https" { + return newSetting(false) + } else if scheme != "" { + return newSetting(true) + } + + return prev +} + +func compressorToCompression(compressor string) Compression { + c, err := convCompression(compressor) + if err != nil { + otel.Handle(fmt.Errorf("%w, using no compression as default", err)) + return NoCompression + } + + return c +} + +// setting is a configuration setting value. +type setting[T any] struct { + Value T + Set bool +} + +// newSetting returns a new setting with the value set. +func newSetting[T any](value T) setting[T] { + return setting[T]{Value: value, Set: true} +} + +// resolver returns an updated setting after applying an resolution operation. +type resolver[T any] func(setting[T]) setting[T] + +// Resolve returns a resolved version of s. +// +// It will apply all the passed fn in the order provided, chaining together the +// return setting to the next input. The setting s is used as the initial +// argument to the first fn. +// +// Each fn needs to validate if it should apply given the Set state of the +// setting. This will not perform any checks on the set state when chaining +// function. +func (s setting[T]) Resolve(fn ...resolver[T]) setting[T] { + for _, f := range fn { + s = f(s) + } + return s +} + +// getEnv returns a resolver that will apply an environment variable value +// associated with the first set key to a setting value. The conv function is +// used to convert between the environment variable value and the setting type. +// +// If the input setting to the resolver is set, the environment variable will +// not be applied. +// +// Any error returned from conv is sent to the OTel ErrorHandler and the +// setting will not be updated. +func getEnv[T any](keys []string, conv func(string) (T, error)) resolver[T] { + return func(s setting[T]) setting[T] { + if s.Set { + // Passed, valid, options have precedence. + return s + } + + for _, key := range keys { + if vStr := os.Getenv(key); vStr != "" { + v, err := conv(vStr) + if err == nil { + s.Value = v + s.Set = true + break + } + otel.Handle(fmt.Errorf("invalid %s value %s: %w", key, vStr, err)) + } + } + return s + } +} + +// fallback returns a resolve that will set a setting value to val if it is not +// already set. +// +// This is usually passed at the end of a resolver chain to ensure a default is +// applied if the setting has not already been set. +func fallback[T any](val T) resolver[T] { + return func(s setting[T]) setting[T] { + if !s.Set { + s.Value = val + s.Set = true + } + return s + } +} + +func isValidHeaderKey(key string) bool { + if key == "" { + return false + } + for _, c := range key { + if !isTokenChar(c) { + return false + } + } + return true +} + +func isTokenChar(c rune) bool { + return c <= unicode.MaxASCII && (unicode.IsLetter(c) || + unicode.IsDigit(c) || + c == '!' || c == '#' || c == '$' || c == '%' || c == '&' || c == '\'' || c == '*' || + c == '+' || c == '-' || c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~') +} diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/doc.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/doc.go new file mode 100644 index 0000000000..67cb814342 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/doc.go @@ -0,0 +1,63 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +/* +Package otlploggrpc provides an OTLP log exporter using gRPC. The exporter uses gRPC to +transport OTLP protobuf payloads. + +All Exporters must be created with [New]. + +The environment variables described below can be used for configuration. + +OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_LOGS_ENDPOINT (default: "https://localhost:4317") - +target to which the exporter sends telemetry. +The target syntax is defined in https://github.com/grpc/grpc/blob/master/doc/naming.md. +The value must contain a scheme ("http" or "https") and host. +The value may additionally contain a port, and a path. +The value should not contain a query string or fragment. +OTEL_EXPORTER_OTLP_LOGS_ENDPOINT takes precedence over OTEL_EXPORTER_OTLP_ENDPOINT. +The configuration can be overridden by [WithEndpoint], [WithEndpointURL], [WithInsecure], and [WithGRPCConn] options. + +OTEL_EXPORTER_OTLP_INSECURE, OTEL_EXPORTER_OTLP_LOGS_INSECURE (default: "false") - +setting "true" disables client transport security for the exporter's gRPC connection. +You can use this only when an endpoint is provided without scheme. +OTEL_EXPORTER_OTLP_LOGS_INSECURE takes precedence over OTEL_EXPORTER_OTLP_INSECURE. +The configuration can be overridden by [WithInsecure], [WithGRPCConn] options. + +OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_LOGS_HEADERS (default: none) - +key-value pairs used as gRPC metadata associated with gRPC requests. +The value is expected to be represented in a format matching the [W3C Baggage HTTP Header Content Format], +except that additional semi-colon delimited metadata is not supported. +Example value: "key1=value1,key2=value2". +OTEL_EXPORTER_OTLP_LOGS_HEADERS takes precedence over OTEL_EXPORTER_OTLP_HEADERS. +The configuration can be overridden by [WithHeaders] option. + +OTEL_EXPORTER_OTLP_TIMEOUT, OTEL_EXPORTER_OTLP_LOGS_TIMEOUT (default: "10000") - +maximum time in milliseconds the OTLP exporter waits for each batch export. +OTEL_EXPORTER_OTLP_LOGS_TIMEOUT takes precedence over OTEL_EXPORTER_OTLP_TIMEOUT. +The configuration can be overridden by [WithTimeout] option. + +OTEL_EXPORTER_OTLP_COMPRESSION, OTEL_EXPORTER_OTLP_LOGS_COMPRESSION (default: none) - +the gRPC compressor the exporter uses. +Supported value: "gzip". +OTEL_EXPORTER_OTLP_LOGS_COMPRESSION takes precedence over OTEL_EXPORTER_OTLP_COMPRESSION. +The configuration can be overridden by [WithCompressor], [WithGRPCConn] options. + +OTEL_EXPORTER_OTLP_CERTIFICATE, OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE (default: none) - +the filepath to the trusted certificate to use when verifying a server's TLS credentials. +OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE takes precedence over OTEL_EXPORTER_OTLP_CERTIFICATE. +The configuration can be overridden by [WithTLSCredentials], [WithGRPCConn] options. + +OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE (default: none) - +the filepath to the client certificate/chain trust for client's private key to use in mTLS communication in PEM format. +OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE takes precedence over OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE. +The configuration can be overridden by [WithTLSCredentials], [WithGRPCConn] options. + +OTEL_EXPORTER_OTLP_CLIENT_KEY, OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY (default: none) - +the filepath to the client's private key to use in mTLS communication in PEM format. +OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY takes precedence over OTEL_EXPORTER_OTLP_CLIENT_KEY. +The configuration can be overridden by [WithTLSCredentials], [WithGRPCConn] option. + +[W3C Baggage HTTP Header Content Format]: https://www.w3.org/TR/baggage/#header-content +*/ +package otlploggrpc // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc" diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/exporter.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/exporter.go new file mode 100644 index 0000000000..898eecf773 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/exporter.go @@ -0,0 +1,94 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package otlploggrpc // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc" + +import ( + "context" + "sync" + "sync/atomic" + + logpb "go.opentelemetry.io/proto/otlp/logs/v1" + + "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/transform" + "go.opentelemetry.io/otel/sdk/log" +) + +type logClient interface { + UploadLogs(ctx context.Context, rl []*logpb.ResourceLogs) error + Shutdown(context.Context) error +} + +// Exporter is a OpenTelemetry log Exporter. It transports log data encoded as +// OTLP protobufs using gRPC. +// All Exporters must be created with [New]. +type Exporter struct { + // Ensure synchronous access to the client across all functionality. + clientMu sync.Mutex + client logClient + + stopped atomic.Bool +} + +// Compile-time check Exporter implements [log.Exporter]. +var _ log.Exporter = (*Exporter)(nil) + +// New returns a new [Exporter]. +// +// It is recommended to use it with a [BatchProcessor] +// or other processor exporting records asynchronously. +func New(_ context.Context, options ...Option) (*Exporter, error) { + cfg := newConfig(options) + c, err := newClient(cfg) + if err != nil { + return nil, err + } + return newExporter(c), nil +} + +func newExporter(c logClient) *Exporter { + var e Exporter + e.client = c + return &e +} + +var transformResourceLogs = transform.ResourceLogs + +// Export transforms and transmits log records to an OTLP receiver. +// +// This method returns nil and drops records if called after Shutdown. +// This method returns an error if the method is canceled by the passed context. +func (e *Exporter) Export(ctx context.Context, records []log.Record) error { + if e.stopped.Load() { + return nil + } + + otlp := transformResourceLogs(records) + if otlp == nil { + return nil + } + + e.clientMu.Lock() + defer e.clientMu.Unlock() + return e.client.UploadLogs(ctx, otlp) +} + +// Shutdown shuts down the Exporter. Calls to Export or ForceFlush will perform +// no operation after this is called. +func (e *Exporter) Shutdown(ctx context.Context) error { + if e.stopped.Swap(true) { + return nil + } + + e.clientMu.Lock() + defer e.clientMu.Unlock() + + err := e.client.Shutdown(ctx) + e.client = newNoopClient() + return err +} + +// ForceFlush does nothing. The Exporter holds no state. +func (*Exporter) ForceFlush(context.Context) error { + return nil +} diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/gen.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/gen.go new file mode 100644 index 0000000000..797bfc95c5 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/gen.go @@ -0,0 +1,20 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// Package internal provides internal functionality for the otlploggrpc +// package. +package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal" + +//go:generate gotmpl --body=../../../../../internal/shared/otlp/observ/target.go.tmpl "--data={ \"pkg\": \"observ\", \"pkg_path\": \"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/observ\" }" --out=observ/target.go +//go:generate gotmpl --body=../../../../../internal/shared/otlp/observ/target_test.go.tmpl "--data={ \"pkg\": \"observ\" }" --out=observ/target_test.go + +//go:generate gotmpl --body=../../../../../internal/shared/x/x.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc\" }" --out=x/x.go +//go:generate gotmpl --body=../../../../../internal/shared/x/x_test.go.tmpl "--data={}" --out=x/x_test.go + +//go:generate gotmpl --body=../../../../../internal/shared/otlp/retry/retry.go.tmpl "--data={}" --out=retry/retry.go +//go:generate gotmpl --body=../../../../../internal/shared/otlp/retry/retry_test.go.tmpl "--data={}" --out=retry/retry_test.go + +//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlplog/transform/attr_test.go.tmpl "--data={}" --out=transform/attr_test.go +//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlplog/transform/log.go.tmpl "--data={}" --out=transform/log.go +//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlplog/transform/log_attr_test.go.tmpl "--data={}" --out=transform/log_attr_test.go +//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlplog/transform/log_test.go.tmpl "--data={}" --out=transform/log_test.go diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/observ/instrumentation.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/observ/instrumentation.go new file mode 100644 index 0000000000..7f2ecab719 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/observ/instrumentation.go @@ -0,0 +1,317 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// Package observ provides observability metrics for OTLP log exporters. +// This is an experimental feature controlled by the x.Observability feature flag. +package observ // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/observ" + +import ( + "context" + "errors" + "fmt" + "sync" + "time" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal" + "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/x" + "go.opentelemetry.io/otel/internal/global" + "go.opentelemetry.io/otel/metric" + semconv "go.opentelemetry.io/otel/semconv/v1.41.0" + "go.opentelemetry.io/otel/semconv/v1.41.0/otelconv" +) + +const ( + // ScopeName is the unique name of the meter used for instrumentation. + ScopeName = "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/observ" + + // Version is the current version of this instrumentation. + // + // This matches the version of the exporter. + Version = internal.Version +) + +var ( + attrsPool = &sync.Pool{ + New: func() any { + const n = 1 + // component.name + 1 + // component.type + 1 + // server.addr + 1 + // server.port + 1 + // error.type + 1 // rpc.grpc.status_code + s := make([]attribute.KeyValue, 0, n) + // Return a pointer to a slice instead of a slice itself + // to avoid allocations on every call. + return &s + }, + } + addOpPool = &sync.Pool{ + New: func() any { + const n = 1 // WithAttributeSet + o := make([]metric.AddOption, 0, n) + return &o + }, + } + recordOptPool = &sync.Pool{ + New: func() any { + const n = 1 // WithAttributeSet + o := make([]metric.RecordOption, 0, n) + return &o + }, + } +) + +func get[T any](p *sync.Pool) *[]T { return p.Get().(*[]T) } +func put[T any](p *sync.Pool, s *[]T) { + clear(*s) // erase elements to allow GC to collect what they refer to. + *s = (*s)[:0] + p.Put(s) +} + +// GetComponentName returns the constant name for the exporter with the +// provided id. +func GetComponentName(id int64) string { + return fmt.Sprintf("%s/%d", otelconv.ComponentTypeOtlpGRPCLogExporter, id) +} + +// getPresetAttrs builds the preset attributes for instrumentation. +func getPresetAttrs(id int64, target string) []attribute.KeyValue { + serverAttrs := ServerAddrAttrs(target) + attrs := make([]attribute.KeyValue, 0, 2+len(serverAttrs)) + + attrs = append( + attrs, + semconv.OTelComponentName(GetComponentName(id)), + semconv.OTelComponentTypeOtlpGRPCLogExporter, + ) + attrs = append(attrs, serverAttrs...) + + return attrs +} + +// Instrumentation is experimental instrumentation for the exporter. +type Instrumentation struct { + logInflightMetric metric.Int64UpDownCounter + logExportedMetric metric.Int64Counter + logExportedDurationMetric metric.Float64Histogram + + presetAttrs []attribute.KeyValue + addOpt metric.AddOption + recOpt metric.RecordOption +} + +// NewInstrumentation returns instrumentation for otlplog grpc exporter. +func NewInstrumentation(id int64, target string) (*Instrumentation, error) { + if !x.Observability.Enabled() { + return nil, nil + } + + i := &Instrumentation{} + + mp := otel.GetMeterProvider() + m := mp.Meter( + ScopeName, + metric.WithInstrumentationVersion(Version), + metric.WithSchemaURL(semconv.SchemaURL), + ) + + var err error + + logInflightMetric, e := otelconv.NewSDKExporterLogInflight(m) + if e != nil { + e = fmt.Errorf("failed to create log inflight metric: %w", e) + err = errors.Join(err, e) + } + i.logInflightMetric = logInflightMetric.Inst() + + logExportedMetric, e := otelconv.NewSDKExporterLogExported(m) + if e != nil { + e = fmt.Errorf("failed to create log exported metric: %w", e) + err = errors.Join(err, e) + } + i.logExportedMetric = logExportedMetric.Inst() + + logOpDurationMetric, e := otelconv.NewSDKExporterOperationDuration(m) + if e != nil { + e = fmt.Errorf("failed to create log operation duration metric: %w", e) + err = errors.Join(err, e) + } + i.logExportedDurationMetric = logOpDurationMetric.Inst() + if err != nil { + return nil, err + } + + i.presetAttrs = getPresetAttrs(id, target) + + i.addOpt = metric.WithAttributeSet(attribute.NewSet(i.presetAttrs...)) + i.recOpt = metric.WithAttributeSet(attribute.NewSet(append( + // Default to OK status code. + []attribute.KeyValue{semconv.RPCResponseStatusCode(codes.OK.String())}, + i.presetAttrs..., + )...)) + return i, nil +} + +// ExportLogs instruments the ExportLogs method of the exporter. It returns +// an [ExportOp] that must have its [ExportOp.End] method called when the +// ExportLogs method returns. +func (i *Instrumentation) ExportLogs(ctx context.Context, count int64) ExportOp { + start := time.Now() + if i.logInflightMetric.Enabled(ctx) { + addOpt := get[metric.AddOption](addOpPool) + defer put(addOpPool, addOpt) + *addOpt = append(*addOpt, i.addOpt) + i.logInflightMetric.Add(ctx, count, *addOpt...) + } + + return ExportOp{ + nLogs: count, + ctx: ctx, + start: start, + inst: i, + } +} + +// ExportOp tracks the operation being observed by [Instrumentation.ExportLogs]. +type ExportOp struct { + nLogs int64 + ctx context.Context + start time.Time + + inst *Instrumentation +} + +// End completes the observation of the operation being observed by a call to +// [Instrumentation.ExportLogs]. +// Any error that is encountered is provided as err. +// +// If err is not nil, all logs will be recorded as failures unless error is of +// type [internal.PartialSuccess]. In the case of a PartialSuccess, the number +// of successfully exported logs will be determined by inspecting the +// RejectedItems field of the PartialSuccess. +func (e ExportOp) End(err error) { + addOpt := get[metric.AddOption](addOpPool) + defer put(addOpPool, addOpt) + *addOpt = append(*addOpt, e.inst.addOpt) + + if e.inst.logInflightMetric.Enabled(e.ctx) { + e.inst.logInflightMetric.Add(e.ctx, -e.nLogs, *addOpt...) + } + success := successful(e.nLogs, err) + if e.inst.logExportedMetric.Enabled(e.ctx) { + e.inst.logExportedMetric.Add(e.ctx, success, *addOpt...) + } + if err != nil && e.inst.logExportedMetric.Enabled(e.ctx) { + // Add the error.type attribute to the attribute set. + attrs := get[attribute.KeyValue](attrsPool) + defer put(attrsPool, attrs) + *attrs = append(*attrs, e.inst.presetAttrs...) + *attrs = append(*attrs, semconv.ErrorType(err)) + + o := metric.WithAttributeSet(attribute.NewSet(*attrs...)) + + // Reset addOpt with new attribute set + *addOpt = append((*addOpt)[:0], o) + + e.inst.logExportedMetric.Add(e.ctx, e.nLogs-success, *addOpt...) + } + + recordOpt := get[metric.RecordOption](recordOptPool) + defer put(recordOptPool, recordOpt) + *recordOpt = append(*recordOpt, e.inst.recordOption(err)) + e.inst.logExportedDurationMetric.Record(e.ctx, time.Since(e.start).Seconds(), *recordOpt...) +} + +func (i *Instrumentation) recordOption(err error) metric.RecordOption { + if err == nil { + return i.recOpt + } + attrs := get[attribute.KeyValue](attrsPool) + defer put(attrsPool, attrs) + + *attrs = append(*attrs, i.presetAttrs...) + code := status.Code(err) + *attrs = append( + *attrs, + semconv.RPCResponseStatusCode(code.String()), + semconv.ErrorType(err), + ) + + return metric.WithAttributeSet(attribute.NewSet(*attrs...)) +} + +// successful returns the number of successfully exported logs out of the n +// that were exported based on the provided error. +// +// If err is nil, n is returned. All logs were successfully exported. +// +// If err is not nil and not an [internal.PartialSuccess] error, 0 is returned. +// It is assumed all logs failed to be exported. +// +// If err is an [internal.PartialSuccess] error, the number of successfully +// exported logs is computed by subtracting the RejectedItems field from n. If +// RejectedItems is negative, n is returned. If RejectedItems is greater than +// n, 0 is returned. +func successful(n int64, err error) int64 { + if err == nil { + return n // All logs successfully exported. + } + // Split rejection calculation so successful is inlineable. + return n - rejectedCount(n, err) +} + +var errPool = sync.Pool{ + New: func() any { + return new(internal.PartialSuccess) + }, +} + +// rejectedCount returns how many out of the n logs exporter were rejected based on +// the provided non-nil err. +func rejectedCount(n int64, err error) int64 { + ps := errPool.Get().(*internal.PartialSuccess) + defer func() { + *ps = internal.PartialSuccess{} // erase fields to allow GC to collect them. + errPool.Put(ps) + }() + + // check for partial success + if errors.As(err, ps) { + return min(max(ps.RejectedItems, 0), n) + } + // all logs exporter + return n +} + +// ServerAddrAttrs is a function that extracts server address and port attributes +// from a target string. +func ServerAddrAttrs(target string) []attribute.KeyValue { + addr, port, err := ParseCanonicalTarget(target) + if err != nil || (addr == "" && port < 0) { + if err != nil { + global.Debug("failed to parse target", "target", target, "error", err) + } + return nil + } + + // Unix domain sockets: return only the path as server.address + if port == -1 { + return []attribute.KeyValue{semconv.ServerAddress(addr)} + } + + // For network addresses, only include port if it's valid (> 0) + if port > 0 { + return []attribute.KeyValue{ + semconv.ServerAddress(addr), + semconv.ServerPort(port), + } + } + + // Port is 0 or invalid, only return address + return []attribute.KeyValue{semconv.ServerAddress(addr)} +} diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/observ/target.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/observ/target.go new file mode 100644 index 0000000000..186f00e52a --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/observ/target.go @@ -0,0 +1,143 @@ +// Code generated by gotmpl. DO NOT MODIFY. +// source: internal/shared/otlp/observ/target.go.tmpl + +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package observ // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/observ" + +import ( + "errors" + "fmt" + "net" + "net/netip" + "strconv" + "strings" +) + +const ( + schemeUnix = "unix" + schemeUnixAbstract = "unix-abstract" +) + +// ParseCanonicalTarget parses a target string and returns the extracted host +// (domain address or IP), the target port, or an error. +// +// If no port is specified, -1 is returned. +// +// If no host is specified, an empty string is returned. +// +// The target string is expected to always have the form +// "://[authority]/". For example: +// - "dns:///example.com:42" +// - "dns://8.8.8.8/example.com:42" +// - "unix:///path/to/socket" +// - "unix-abstract:///socket-name" +// - "passthrough:///192.34.2.1:42" +// +// The target is expected to come from the CanonicalTarget method of a gRPC +// Client. +func ParseCanonicalTarget(target string) (string, int, error) { + const sep = "://" + + // Find scheme. Do not allocate the string by using url.Parse. + idx := strings.Index(target, sep) + if idx == -1 { + return "", -1, fmt.Errorf("invalid target %q: missing scheme", target) + } + scheme, endpoint := target[:idx], target[idx+len(sep):] + + // Check for unix schemes. + if scheme == schemeUnix || scheme == schemeUnixAbstract { + return parseUnix(endpoint) + } + + // Strip leading slash and any authority. + if i := strings.Index(endpoint, "/"); i != -1 { + endpoint = endpoint[i+1:] + } + + // DNS, passthrough, and custom resolvers. + return parseEndpoint(endpoint) +} + +// parseUnix parses unix socket targets. +func parseUnix(endpoint string) (string, int, error) { + // Format: unix[-abstract]://path + // + // We should have "/path" (empty authority) if valid. + if len(endpoint) >= 1 && endpoint[0] == '/' { + // Return the full path including leading slash. + return endpoint, -1, nil + } + + // If there's no leading slash, it means there might be an authority + // Check for authority case (should error): "authority/path" + if slashIdx := strings.Index(endpoint, "/"); slashIdx > 0 { + return "", -1, fmt.Errorf("invalid (non-empty) authority: %s", endpoint[:slashIdx]) + } + + return "", -1, errors.New("invalid unix target format") +} + +// parseEndpoint parses an endpoint from a gRPC target. +// +// It supports the following formats: +// - "host" +// - "host%zone" +// - "host:port" +// - "host%zone:port" +// - "ipv4" +// - "ipv4%zone" +// - "ipv4:port" +// - "ipv4%zone:port" +// - "ipv6" +// - "ipv6%zone" +// - "[ipv6]" +// - "[ipv6%zone]" +// - "[ipv6]:port" +// - "[ipv6%zone]:port" +// +// It returns the host or host%zone (domain address or IP), the port (or -1 if +// not specified), or an error if the input is not a valid. +func parseEndpoint(endpoint string) (string, int, error) { + // First check if the endpoint is just an IP address. + if ip := parseIP(endpoint); ip != "" { + return ip, -1, nil + } + + // If there's no colon, there is no port (IPv6 with no port checked above). + if !strings.Contains(endpoint, ":") { + return endpoint, -1, nil + } + + host, portStr, err := net.SplitHostPort(endpoint) + if err != nil { + return "", -1, fmt.Errorf("invalid host:port %q: %w", endpoint, err) + } + + const base, bitSize = 10, 16 + port16, err := strconv.ParseUint(portStr, base, bitSize) + if err != nil { + return "", -1, fmt.Errorf("invalid port %q: %w", portStr, err) + } + port := int(port16) // port is guaranteed to be in the range [0, 65535]. + + return host, port, nil +} + +// parseIP attempts to parse the entire endpoint as an IP address. +// It returns the normalized string form of the IP if successful, +// or an empty string if parsing fails. +func parseIP(ip string) string { + // Strip leading and trailing brackets for IPv6 addresses. + if len(ip) >= 2 && ip[0] == '[' && ip[len(ip)-1] == ']' { + ip = ip[1 : len(ip)-1] + } + addr, err := netip.ParseAddr(ip) + if err != nil { + return "" + } + // Return the normalized string form of the IP. + return addr.String() +} diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/partialsuccess.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/partialsuccess.go new file mode 100644 index 0000000000..076f05fcf0 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/partialsuccess.go @@ -0,0 +1,43 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal" + +import "fmt" + +// PartialSuccess represents the underlying error for all handling +// OTLP partial success messages. Use `errors.Is(err, +// PartialSuccess{})` to test whether an error passed to the OTel +// error handler belongs to this category. +type PartialSuccess struct { + ErrorMessage string + RejectedItems int64 + RejectedKind string +} + +var _ error = PartialSuccess{} + +// Error implements the error interface. +func (ps PartialSuccess) Error() string { + msg := ps.ErrorMessage + if msg == "" { + msg = "empty message" + } + return fmt.Sprintf("OTLP partial success: %s (%d %s rejected)", msg, ps.RejectedItems, ps.RejectedKind) +} + +// Is supports the errors.Is() interface. +func (PartialSuccess) Is(err error) bool { + _, ok := err.(PartialSuccess) + return ok +} + +// LogPartialSuccessError returns an error describing a partial success +// response for the log signal. +func LogPartialSuccessError(itemsRejected int64, errorMessage string) error { + return PartialSuccess{ + ErrorMessage: errorMessage, + RejectedItems: itemsRejected, + RejectedKind: "logs", + } +} diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/retry/retry.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/retry/retry.go new file mode 100644 index 0000000000..1a5684d6e8 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/retry/retry.go @@ -0,0 +1,146 @@ +// Code generated by gotmpl. DO NOT MODIFY. +// source: internal/shared/otlp/retry/retry.go.tmpl + +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// Package retry provides request retry functionality that can perform +// configurable exponential backoff for transient errors and honor any +// explicit throttle responses received. +package retry // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/retry" + +import ( + "context" + "fmt" + "time" + + "github.com/cenkalti/backoff/v5" +) + +// DefaultConfig are the recommended defaults to use. +var DefaultConfig = Config{ + Enabled: true, + InitialInterval: 5 * time.Second, + MaxInterval: 30 * time.Second, + MaxElapsedTime: time.Minute, +} + +// Config defines configuration for retrying batches in case of export failure +// using an exponential backoff. +type Config struct { + // Enabled indicates whether to not retry sending batches in case of + // export failure. + Enabled bool + // InitialInterval the time to wait after the first failure before + // retrying. + InitialInterval time.Duration + // MaxInterval is the upper bound on backoff interval. Once this value is + // reached the delay between consecutive retries will always be + // `MaxInterval`. + MaxInterval time.Duration + // MaxElapsedTime is the maximum amount of time (including retries) spent + // trying to send a request/batch. Once this value is reached, the data + // is discarded. + MaxElapsedTime time.Duration +} + +// RequestFunc wraps a request with retry logic. +type RequestFunc func(context.Context, func(context.Context) error) error + +// EvaluateFunc returns if an error is retry-able and if an explicit throttle +// duration should be honored that was included in the error. +// +// The function must return true if the error argument is retry-able, +// otherwise it must return false for the first return parameter. +// +// The function must return a non-zero time.Duration if the error contains +// explicit throttle duration that should be honored, otherwise it must return +// a zero valued time.Duration. +type EvaluateFunc func(error) (bool, time.Duration) + +// RequestFunc returns a RequestFunc using the evaluate function to determine +// if requests can be retried and based on the exponential backoff +// configuration of c. +func (c Config) RequestFunc(evaluate EvaluateFunc) RequestFunc { + if !c.Enabled { + return func(ctx context.Context, fn func(context.Context) error) error { + return fn(ctx) + } + } + + return func(ctx context.Context, fn func(context.Context) error) error { + // Do not use NewExponentialBackOff since it calls Reset and the code here + // must call Reset after changing the InitialInterval (this saves an + // unnecessary call to Now). + b := &backoff.ExponentialBackOff{ + InitialInterval: c.InitialInterval, + RandomizationFactor: backoff.DefaultRandomizationFactor, + Multiplier: backoff.DefaultMultiplier, + MaxInterval: c.MaxInterval, + } + b.Reset() + + maxElapsedTime := c.MaxElapsedTime + startTime := time.Now() + + for { + err := fn(ctx) + if err == nil { + return nil + } + + retryable, throttle := evaluate(err) + if !retryable { + return err + } + + // Check if context is canceled before attempting to wait and retry. + if ctx.Err() != nil { + return fmt.Errorf("%w: %w", ctx.Err(), err) + } + + if maxElapsedTime != 0 && time.Since(startTime) > maxElapsedTime { + return fmt.Errorf("max retry time elapsed: %w", err) + } + + // Wait for the greater of the backoff or throttle delay. + bOff := b.NextBackOff() + delay := max(throttle, bOff) + + elapsed := time.Since(startTime) + if maxElapsedTime != 0 && elapsed+throttle > maxElapsedTime { + return fmt.Errorf("max retry time would elapse: %w", err) + } + + if ctxErr := waitFunc(ctx, delay); ctxErr != nil { + return fmt.Errorf("%w: %w", ctxErr, err) + } + } + } +} + +// Allow override for testing. +var waitFunc = wait + +// wait takes the caller's context, and the amount of time to wait. It will +// return nil if the timer fires before or at the same time as the context's +// deadline. This indicates that the call can be retried. +func wait(ctx context.Context, delay time.Duration) error { + timer := time.NewTimer(delay) + defer timer.Stop() + + select { + case <-ctx.Done(): + // Handle the case where the timer and context deadline end + // simultaneously by prioritizing the timer expiration nil value + // response. + select { + case <-timer.C: + default: + return context.Cause(ctx) + } + case <-timer.C: + } + + return nil +} diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/transform/log.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/transform/log.go new file mode 100644 index 0000000000..aa6eb510be --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/transform/log.go @@ -0,0 +1,411 @@ +// Code generated by gotmpl. DO NOT MODIFY. +// source: internal/shared/otlp/otlplog/transform/log.go.tmpl + +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// Package transform provides transformation functionality from the +// sdk/log data-types into OTLP data-types. +package transform // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/transform" + +import ( + "time" + + cpb "go.opentelemetry.io/proto/otlp/common/v1" + lpb "go.opentelemetry.io/proto/otlp/logs/v1" + rpb "go.opentelemetry.io/proto/otlp/resource/v1" + + "go.opentelemetry.io/otel/attribute" + api "go.opentelemetry.io/otel/log" + "go.opentelemetry.io/otel/sdk/instrumentation" + "go.opentelemetry.io/otel/sdk/log" +) + +// ResourceLogs returns an slice of OTLP ResourceLogs generated from records. +func ResourceLogs(records []log.Record) []*lpb.ResourceLogs { + if len(records) == 0 { + return nil + } + + resMap := make(map[attribute.Distinct]*lpb.ResourceLogs) + + type key struct { + r attribute.Distinct + is instrumentation.Scope + } + scopeMap := make(map[key]*lpb.ScopeLogs) + + var resources int + for _, r := range records { + res := r.Resource() + rKey := res.Equivalent() + scope := r.InstrumentationScope() + k := key{ + r: rKey, + is: scope, + } + sl, iOk := scopeMap[k] + if !iOk { + sl = new(lpb.ScopeLogs) + var emptyScope instrumentation.Scope + if scope != emptyScope { + sl.Scope = &cpb.InstrumentationScope{ + Name: scope.Name, + Version: scope.Version, + Attributes: AttrIter(scope.Attributes.Iter()), + } + sl.SchemaUrl = scope.SchemaURL + } + scopeMap[k] = sl + } + + sl.LogRecords = append(sl.LogRecords, LogRecord(r)) + rl, rOk := resMap[rKey] + if !rOk { + resources++ + rl = new(lpb.ResourceLogs) + if res.Len() > 0 { + rl.Resource = &rpb.Resource{ + Attributes: AttrIter(res.Iter()), + } + } + rl.SchemaUrl = res.SchemaURL() + resMap[rKey] = rl + } + if !iOk { + rl.ScopeLogs = append(rl.ScopeLogs, sl) + } + } + + // Transform the categorized map into a slice + resLogs := make([]*lpb.ResourceLogs, 0, resources) + for _, rl := range resMap { + resLogs = append(resLogs, rl) + } + + return resLogs +} + +// LogRecord returns an OTLP LogRecord generated from record. +func LogRecord(record log.Record) *lpb.LogRecord { + r := &lpb.LogRecord{ + TimeUnixNano: timeUnixNano(record.Timestamp()), + ObservedTimeUnixNano: timeUnixNano(record.ObservedTimestamp()), + EventName: record.EventName(), + SeverityNumber: SeverityNumber(record.Severity()), + SeverityText: record.SeverityText(), + Body: LogAttrValue(record.Body()), + Attributes: make([]*cpb.KeyValue, 0, record.AttributesLen()), + Flags: uint32(record.TraceFlags()), + // TODO: DroppedAttributesCount: /* ... */, + } + record.WalkAttributes(func(kv api.KeyValue) bool { + r.Attributes = append(r.Attributes, LogAttr(kv)) + return true + }) + if tID := record.TraceID(); tID.IsValid() { + r.TraceId = tID[:] + } + if sID := record.SpanID(); sID.IsValid() { + r.SpanId = sID[:] + } + return r +} + +// timeUnixNano returns t as a Unix time, the number of nanoseconds elapsed +// since January 1, 1970 UTC as uint64. The result is undefined if the Unix +// time in nanoseconds cannot be represented by an int64 (a date before the +// year 1678 or after 2262). timeUnixNano on the zero Time returns 0. The +// result does not depend on the location associated with t. +func timeUnixNano(t time.Time) uint64 { + nano := t.UnixNano() + if nano < 0 { + return 0 + } + return uint64(nano) // nolint:gosec // Overflow checked. +} + +// AttrIter transforms an [attribute.Iterator] into OTLP key-values. +func AttrIter(iter attribute.Iterator) []*cpb.KeyValue { + l := iter.Len() + if l == 0 { + return nil + } + + out := make([]*cpb.KeyValue, 0, l) + for iter.Next() { + out = append(out, Attr(iter.Attribute())) + } + return out +} + +// Attrs transforms a slice of [attribute.KeyValue] into OTLP key-values. +func Attrs(attrs []attribute.KeyValue) []*cpb.KeyValue { + if len(attrs) == 0 { + return nil + } + + out := make([]*cpb.KeyValue, 0, len(attrs)) + for _, kv := range attrs { + out = append(out, Attr(kv)) + } + return out +} + +// Attr transforms an [attribute.KeyValue] into an OTLP key-value. +func Attr(kv attribute.KeyValue) *cpb.KeyValue { + return &cpb.KeyValue{Key: string(kv.Key), Value: AttrValue(kv.Value)} +} + +// AttrValue transforms an [attribute.Value] into an OTLP AnyValue. +func AttrValue(v attribute.Value) *cpb.AnyValue { + av := new(cpb.AnyValue) + switch v.Type() { + case attribute.BOOL: + av.Value = &cpb.AnyValue_BoolValue{ + BoolValue: v.AsBool(), + } + case attribute.BOOLSLICE: + av.Value = &cpb.AnyValue_ArrayValue{ + ArrayValue: &cpb.ArrayValue{ + Values: boolSliceValues(v.AsBoolSlice()), + }, + } + case attribute.INT64: + av.Value = &cpb.AnyValue_IntValue{ + IntValue: v.AsInt64(), + } + case attribute.INT64SLICE: + av.Value = &cpb.AnyValue_ArrayValue{ + ArrayValue: &cpb.ArrayValue{ + Values: int64SliceValues(v.AsInt64Slice()), + }, + } + case attribute.FLOAT64: + av.Value = &cpb.AnyValue_DoubleValue{ + DoubleValue: v.AsFloat64(), + } + case attribute.FLOAT64SLICE: + av.Value = &cpb.AnyValue_ArrayValue{ + ArrayValue: &cpb.ArrayValue{ + Values: float64SliceValues(v.AsFloat64Slice()), + }, + } + case attribute.STRING: + av.Value = &cpb.AnyValue_StringValue{ + StringValue: v.AsString(), + } + case attribute.BYTESLICE: + av.Value = &cpb.AnyValue_BytesValue{ + BytesValue: v.AsByteSlice(), + } + case attribute.SLICE: + av.Value = &cpb.AnyValue_ArrayValue{ + ArrayValue: &cpb.ArrayValue{ + Values: attrValues(v.AsSlice()), + }, + } + case attribute.STRINGSLICE: + av.Value = &cpb.AnyValue_ArrayValue{ + ArrayValue: &cpb.ArrayValue{ + Values: stringSliceValues(v.AsStringSlice()), + }, + } + case attribute.EMPTY: + default: + av.Value = &cpb.AnyValue_StringValue{ + StringValue: "INVALID", + } + } + return av +} + +func boolSliceValues(vals []bool) []*cpb.AnyValue { + converted := make([]*cpb.AnyValue, len(vals)) + for i, v := range vals { + converted[i] = &cpb.AnyValue{ + Value: &cpb.AnyValue_BoolValue{ + BoolValue: v, + }, + } + } + return converted +} + +func int64SliceValues(vals []int64) []*cpb.AnyValue { + converted := make([]*cpb.AnyValue, len(vals)) + for i, v := range vals { + converted[i] = &cpb.AnyValue{ + Value: &cpb.AnyValue_IntValue{ + IntValue: v, + }, + } + } + return converted +} + +func float64SliceValues(vals []float64) []*cpb.AnyValue { + converted := make([]*cpb.AnyValue, len(vals)) + for i, v := range vals { + converted[i] = &cpb.AnyValue{ + Value: &cpb.AnyValue_DoubleValue{ + DoubleValue: v, + }, + } + } + return converted +} + +func stringSliceValues(vals []string) []*cpb.AnyValue { + converted := make([]*cpb.AnyValue, len(vals)) + for i, v := range vals { + converted[i] = &cpb.AnyValue{ + Value: &cpb.AnyValue_StringValue{ + StringValue: v, + }, + } + } + return converted +} + +func attrValues(vals []attribute.Value) []*cpb.AnyValue { + converted := make([]*cpb.AnyValue, len(vals)) + for i, v := range vals { + converted[i] = AttrValue(v) + } + return converted +} + +// LogAttrs transforms a slice of [api.KeyValue] into OTLP key-values. +func LogAttrs(attrs []api.KeyValue) []*cpb.KeyValue { + if len(attrs) == 0 { + return nil + } + + out := make([]*cpb.KeyValue, 0, len(attrs)) + for _, kv := range attrs { + out = append(out, LogAttr(kv)) + } + return out +} + +// LogAttr transforms an [api.KeyValue] into an OTLP key-value. +func LogAttr(attr api.KeyValue) *cpb.KeyValue { + return &cpb.KeyValue{ + Key: attr.Key, + Value: LogAttrValue(attr.Value), + } +} + +// LogAttrValues transforms a slice of [api.Value] into an OTLP []AnyValue. +func LogAttrValues(vals []api.Value) []*cpb.AnyValue { + if len(vals) == 0 { + return nil + } + + out := make([]*cpb.AnyValue, 0, len(vals)) + for _, v := range vals { + out = append(out, LogAttrValue(v)) + } + return out +} + +// LogAttrValue transforms an [api.Value] into an OTLP AnyValue. +func LogAttrValue(v api.Value) *cpb.AnyValue { + av := new(cpb.AnyValue) + switch v.Kind() { + case api.KindBool: + av.Value = &cpb.AnyValue_BoolValue{ + BoolValue: v.AsBool(), + } + case api.KindInt64: + av.Value = &cpb.AnyValue_IntValue{ + IntValue: v.AsInt64(), + } + case api.KindFloat64: + av.Value = &cpb.AnyValue_DoubleValue{ + DoubleValue: v.AsFloat64(), + } + case api.KindString: + av.Value = &cpb.AnyValue_StringValue{ + StringValue: v.AsString(), + } + case api.KindBytes: + av.Value = &cpb.AnyValue_BytesValue{ + BytesValue: v.AsBytes(), + } + case api.KindSlice: + av.Value = &cpb.AnyValue_ArrayValue{ + ArrayValue: &cpb.ArrayValue{ + Values: LogAttrValues(v.AsSlice()), + }, + } + case api.KindMap: + av.Value = &cpb.AnyValue_KvlistValue{ + KvlistValue: &cpb.KeyValueList{ + Values: LogAttrs(v.AsMap()), + }, + } + case api.KindEmpty: + default: + av.Value = &cpb.AnyValue_StringValue{ + StringValue: "INVALID", + } + } + return av +} + +// SeverityNumber transforms a [log.Severity] into an OTLP SeverityNumber. +func SeverityNumber(s api.Severity) lpb.SeverityNumber { + switch s { + case api.SeverityTrace: + return lpb.SeverityNumber_SEVERITY_NUMBER_TRACE + case api.SeverityTrace2: + return lpb.SeverityNumber_SEVERITY_NUMBER_TRACE2 + case api.SeverityTrace3: + return lpb.SeverityNumber_SEVERITY_NUMBER_TRACE3 + case api.SeverityTrace4: + return lpb.SeverityNumber_SEVERITY_NUMBER_TRACE4 + case api.SeverityDebug: + return lpb.SeverityNumber_SEVERITY_NUMBER_DEBUG + case api.SeverityDebug2: + return lpb.SeverityNumber_SEVERITY_NUMBER_DEBUG2 + case api.SeverityDebug3: + return lpb.SeverityNumber_SEVERITY_NUMBER_DEBUG3 + case api.SeverityDebug4: + return lpb.SeverityNumber_SEVERITY_NUMBER_DEBUG4 + case api.SeverityInfo: + return lpb.SeverityNumber_SEVERITY_NUMBER_INFO + case api.SeverityInfo2: + return lpb.SeverityNumber_SEVERITY_NUMBER_INFO2 + case api.SeverityInfo3: + return lpb.SeverityNumber_SEVERITY_NUMBER_INFO3 + case api.SeverityInfo4: + return lpb.SeverityNumber_SEVERITY_NUMBER_INFO4 + case api.SeverityWarn: + return lpb.SeverityNumber_SEVERITY_NUMBER_WARN + case api.SeverityWarn2: + return lpb.SeverityNumber_SEVERITY_NUMBER_WARN2 + case api.SeverityWarn3: + return lpb.SeverityNumber_SEVERITY_NUMBER_WARN3 + case api.SeverityWarn4: + return lpb.SeverityNumber_SEVERITY_NUMBER_WARN4 + case api.SeverityError: + return lpb.SeverityNumber_SEVERITY_NUMBER_ERROR + case api.SeverityError2: + return lpb.SeverityNumber_SEVERITY_NUMBER_ERROR2 + case api.SeverityError3: + return lpb.SeverityNumber_SEVERITY_NUMBER_ERROR3 + case api.SeverityError4: + return lpb.SeverityNumber_SEVERITY_NUMBER_ERROR4 + case api.SeverityFatal: + return lpb.SeverityNumber_SEVERITY_NUMBER_FATAL + case api.SeverityFatal2: + return lpb.SeverityNumber_SEVERITY_NUMBER_FATAL2 + case api.SeverityFatal3: + return lpb.SeverityNumber_SEVERITY_NUMBER_FATAL3 + case api.SeverityFatal4: + return lpb.SeverityNumber_SEVERITY_NUMBER_FATAL4 + } + return lpb.SeverityNumber_SEVERITY_NUMBER_UNSPECIFIED +} diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/version.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/version.go new file mode 100644 index 0000000000..9dffad62cf --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/version.go @@ -0,0 +1,8 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal" + +// Version is the current release version of the OpenTelemetry otlploggrpc +// exporter in use. +const Version = "0.20.0" diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/x/README.md b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/x/README.md new file mode 100644 index 0000000000..7d73c7e7db --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/x/README.md @@ -0,0 +1,36 @@ +# Experimental Features + +The `otlploggrpc` exporter contains features that have not yet stabilized in the OpenTelemetry specification. +These features are added to the `otlploggrpc` exporter prior to stabilization in the specification so that users can start experimenting with them and provide feedback. + +These features may change in backwards incompatible ways as feedback is applied. +See the [Compatibility and Stability](#compatibility-and-stability) section for more information. + +## Features + +- [Observability](#observability) + +### Observability + +The `otlploggrpc` exporter can be configured to provide observability about itself using OpenTelemetry metrics. + +To opt-in, set the environment variable `OTEL_GO_X_OBSERVABILITY` to `true`. + +When enabled, the exporter will create the following metrics using the global `MeterProvider`: + +- `otel.sdk.exporter.log.inflight` +- `otel.sdk.exporter.log.exported` +- `otel.sdk.exporter.operation.duration` + +Please see the [Semantic conventions for OpenTelemetry SDK metrics] documentation for more details on these metrics. + +[Semantic conventions for OpenTelemetry SDK metrics]: https://github.com/open-telemetry/semantic-conventions/blob/v1.36.0/docs/otel/sdk-metrics.md + +## Compatibility and Stability + +Experimental features do not fall within the scope of the OpenTelemetry Go versioning and stability [policy](../../../../../../VERSIONING.md). +These features may be removed or modified in successive version releases, including patch versions. + +When an experimental feature is promoted to a stable feature, a migration path will be included in the changelog entry of the release. +There is no guarantee that any environment variable feature flags that enabled the experimental feature will be supported by the stable version. +If they are supported, they may be accompanied with a deprecation notice stating a timeline for the removal of that support. diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/x/features.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/x/features.go new file mode 100644 index 0000000000..0ed1c81aa4 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/x/features.go @@ -0,0 +1,23 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// Package x documents experimental features for [go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc]. +package x // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/x" + +import "strings" + +// Observability is an experimental feature flag that determines if exporter +// observability metrics are enabled. +// +// To enable this feature set the OTEL_GO_X_OBSERVABILITY environment variable +// to the case-insensitive string value of "true" (i.e. "True" and "TRUE" +// will also enable this). +var Observability = newFeature( + []string{"OBSERVABILITY"}, + func(v string) (string, bool) { + if strings.EqualFold(v, "true") { + return v, true + } + return "", false + }, +) diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/x/x.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/x/x.go new file mode 100644 index 0000000000..e2d50cedeb --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/x/x.go @@ -0,0 +1,58 @@ +// Code generated by gotmpl. DO NOT MODIFY. +// source: internal/shared/x/x.go.tmpl + +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// Package x documents experimental features for [go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc]. +package x // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/x" + +import ( + "os" +) + +// Feature is an experimental feature control flag. It provides a uniform way +// to interact with these feature flags and parse their values. +type Feature[T any] struct { + keys []string + parse func(v string) (T, bool) +} + +func newFeature[T any](suffix []string, parse func(string) (T, bool)) Feature[T] { + const envKeyRoot = "OTEL_GO_X_" + keys := make([]string, 0, len(suffix)) + for _, s := range suffix { + keys = append(keys, envKeyRoot+s) + } + return Feature[T]{ + keys: keys, + parse: parse, + } +} + +// Keys returns the environment variable keys that can be set to enable the +// feature. +func (f Feature[T]) Keys() []string { return f.keys } + +// Lookup returns the user configured value for the feature and true if the +// user has enabled the feature. Otherwise, if the feature is not enabled, a +// zero-value and false are returned. +func (f Feature[T]) Lookup() (v T, ok bool) { + // https://github.com/open-telemetry/opentelemetry-specification/blob/62effed618589a0bec416a87e559c0a9d96289bb/specification/configuration/sdk-environment-variables.md#parsing-empty-value + // + // > The SDK MUST interpret an empty value of an environment variable the + // > same way as when the variable is unset. + for _, key := range f.keys { + vRaw := os.Getenv(key) + if vRaw != "" { + return f.parse(vRaw) + } + } + return v, ok +} + +// Enabled reports whether the feature is enabled. +func (f Feature[T]) Enabled() bool { + _, ok := f.Lookup() + return ok +} diff --git a/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/version.go b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/version.go new file mode 100644 index 0000000000..818ecf9e9d --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/version.go @@ -0,0 +1,9 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package otlploggrpc // import "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc" + +// Version is the current release version of the OpenTelemetry OTLP over gRPC logs exporter in use. +func Version() string { + return "0.14.0" +} diff --git a/vendor/go.opentelemetry.io/otel/log/DESIGN.md b/vendor/go.opentelemetry.io/otel/log/DESIGN.md new file mode 100644 index 0000000000..47d39d34bf --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/log/DESIGN.md @@ -0,0 +1,634 @@ +# Logs API + +## Abstract + +`go.opentelemetry.io/otel/log` provides +[Logs API](https://opentelemetry.io/docs/specs/otel/logs/api/). + +The prototype was created in +[#4725](https://github.com/open-telemetry/opentelemetry-go/pull/4725). + +## Background + +The key challenge is to create a performant API compliant with the [specification](https://opentelemetry.io/docs/specs/otel/logs/api/) +with an intuitive and user friendly design. +Performance is seen as one of the most important characteristics of logging libraries in Go. + +## Design + +This proposed design aims to: + +- be specification compliant, +- be similar to Trace and Metrics API, +- take advantage of both OpenTelemetry and `slog` experience to achieve acceptable performance. + +### Module structure + +The API is published as a single `go.opentelemetry.io/otel/log` Go module. + +The package structure is similar to Trace API and Metrics API. +The Go module consists of the following packages: + +- `go.opentelemetry.io/otel/log` +- `go.opentelemetry.io/otel/log/embedded` +- `go.opentelemetry.io/otel/log/logtest` +- `go.opentelemetry.io/otel/log/noop` + +Rejected alternative: + +- [Reuse slog](#reuse-slog) + +### LoggerProvider + +The [`LoggerProvider` abstraction](https://opentelemetry.io/docs/specs/otel/logs/api/#loggerprovider) +is defined as `LoggerProvider` interface in [provider.go](provider.go). + +The specification may add new operations to `LoggerProvider`. +The interface may have methods added without a package major version bump. +This embeds `embedded.LoggerProvider` to help inform an API implementation +author about this non-standard API evolution. +This approach is already used in Trace API and Metrics API. + +#### LoggerProvider.Logger + +The `Logger` method implements the [`Get a Logger` operation](https://opentelemetry.io/docs/specs/otel/logs/api/#get-a-logger). + +The required `name` parameter is accepted as a `string` method argument. + +The `LoggerOption` options are defined to support optional parameters. + +Implementation requirements: + +- The [specification requires](https://opentelemetry.io/docs/specs/otel/logs/api/#concurrency-requirements) + the method to be safe to be called concurrently. + +- The method should use some default name if the passed name is empty + in order to meet the [specification's SDK requirement](https://opentelemetry.io/docs/specs/otel/logs/sdk/#logger-creation) + to return a working logger when an invalid name is passed + as well as to resemble the behavior of getting tracers and meters. + +`Logger` can be extended by adding new `LoggerOption` options +and adding new exported fields to the `LoggerConfig` struct. +This design is already used in Trace API for getting tracers +and in Metrics API for getting meters. + +Rejected alternative: + +- [Passing struct as parameter to LoggerProvider.Logger](#passing-struct-as-parameter-to-loggerproviderlogger). + +### Logger + +The [`Logger` abstraction](https://opentelemetry.io/docs/specs/otel/logs/api/#logger) +is defined as `Logger` interface in [logger.go](logger.go). + +The specification may add new operations to `Logger`. +The interface may have methods added without a package major version bump. +This embeds `embedded.Logger` to help inform an API implementation +author about this non-standard API evolution. +This approach is already used in Trace API and Metrics API. + +### Logger.Emit + +The `Emit` method implements the [`Emit a LogRecord` operation](https://opentelemetry.io/docs/specs/otel/logs/api/#emit-a-logrecord). + +[`Context` associated with the `LogRecord`](https://opentelemetry.io/docs/specs/otel/context/) +is accepted as a `context.Context` method argument. + +Calls to `Emit` are supposed to be on the hot path. +Therefore, in order to reduce the number of heap allocations, +the [`LogRecord` abstraction](https://opentelemetry.io/docs/specs/otel/logs/api/#emit-a-logrecord), +is defined as `Record` struct in [record.go](record.go). + +[`Timestamp`](https://opentelemetry.io/docs/specs/otel/logs/data-model/#field-timestamp) +is accessed using following methods: + +```go +func (r *Record) Timestamp() time.Time +func (r *Record) SetTimestamp(t time.Time) +``` + +[`ObservedTimestamp`](https://opentelemetry.io/docs/specs/otel/logs/data-model/#field-observedtimestamp) +is accessed using following methods: + +```go +func (r *Record) ObservedTimestamp() time.Time +func (r *Record) SetObservedTimestamp(t time.Time) +``` + +[`EventName`](https://opentelemetry.io/docs/specs/otel/logs/data-model/#field-eventname) +is accessed using following methods: + +```go +func (r *Record) EventName() string +func (r *Record) SetEventName(s string) +``` + +[`SeverityNumber`](https://opentelemetry.io/docs/specs/otel/logs/data-model/#field-severitynumber) +is accessed using following methods: + +```go +func (r *Record) Severity() Severity +func (r *Record) SetSeverity(s Severity) +``` + +`Severity` type is defined in [severity.go](severity.go). +The constants are are based on +[Displaying Severity recommendation](https://opentelemetry.io/docs/specs/otel/logs/data-model/#displaying-severity). +Additionally, `Severity[Level]` constants are defined to make the API more readable and user friendly. + +[`SeverityText`](https://opentelemetry.io/docs/specs/otel/logs/data-model/#field-severitytext) +is accessed using following methods: + +```go +func (r *Record) SeverityText() string +func (r *Record) SetSeverityText(s string) +``` + +[`Body`](https://opentelemetry.io/docs/specs/otel/logs/data-model/#field-body) +is accessed using following methods: + +```go +func (r *Record) Body() Value +func (r *Record) SetBody(v Value) +``` + +[Log record attributes](https://opentelemetry.io/docs/specs/otel/logs/data-model/#field-attributes) +are accessed using following methods: + +```go +func (r *Record) WalkAttributes(f func(KeyValue) bool) +func (r *Record) AddAttributes(attrs ...KeyValue) +``` + +`Record` has a `AttributesLen` method that returns +the number of attributes to allow slice preallocation +when converting records to a different representation: + +```go +func (r *Record) AttributesLen() int +``` + +The records attributes design and implementation is based on +[`slog.Record`](https://pkg.go.dev/log/slog#Record). +It allows achieving high-performance access and manipulation of the attributes +while keeping the API user friendly. +It relieves the user from making his own improvements +for reducing the number of allocations when passing attributes. + +The abstractions described in +[the specification](https://opentelemetry.io/docs/specs/otel/logs/#new-first-party-application-logs) +are defined in [keyvalue.go](keyvalue.go). + +`Value` is representing `any`. +`KeyValue` is representing a key(string)-value(`any`) pair. + +`Kind` is an enumeration used for specifying the underlying value type. +`KindEmpty` is used for an empty (zero) value. +`KindBool` is used for boolean value. +`KindFloat64` is used for a double precision floating point (IEEE 754-1985) value. +`KindInt64` is used for a signed integer value. +`KindString` is used for a string value. +`KindBytes` is used for a slice of bytes (in spec: A byte array). +`KindSlice` is used for a slice of values (in spec: an array (a list) of any values). +`KindMap` is used for a slice of key-value pairs (in spec: `map`). + +These types are defined in `go.opentelemetry.io/otel/log` package +as they are tightly coupled with the API and different from common attributes. + +The internal implementation of `Value` is based on +[`slog.Value`](https://pkg.go.dev/log/slog#Value) +and the API is mostly inspired by +[`attribute.Value`](https://pkg.go.dev/go.opentelemetry.io/otel/attribute#Value). +The benchmarks[^1] show that the implementation is more performant than +[`attribute.Value`](https://pkg.go.dev/go.opentelemetry.io/otel/attribute#Value). + +The value accessors (`func (v Value) As[Kind]` methods) must not panic, +as it would violate the [specification](https://opentelemetry.io/docs/specs/otel/error-handling/): + +> API methods MUST NOT throw unhandled exceptions when used incorrectly by end +> users. The API and SDK SHOULD provide safe defaults for missing or invalid +> arguments. [...] Whenever the library suppresses an error that would otherwise +> have been exposed to the user, the library SHOULD log the error using +> language-specific conventions. + +Therefore, the value accessors should return a zero value +and log an error when a bad accessor is called. + +The `Severity`, `Kind`, `Value`, `KeyValue` may implement +the [`fmt.Stringer`](https://pkg.go.dev/fmt#Stringer) interface. +However, it is not needed for the first stable release +and the `String` methods can be added later. + +The caller must not subsequently mutate the record passed to `Emit`. +This would allow the implementation to not clone the record, +but simply retain, modify or discard it. +The implementation may still choose to clone the record or copy its attributes +if it needs to retain or modify it, +e.g. in case of asynchronous processing to eliminate the possibility of data races, +because the user can technically reuse the record and add new attributes +after the call (even when the documentation says that the caller must not do it). + +Implementation requirements: + +- The [specification requires](https://opentelemetry.io/docs/specs/otel/logs/api/#concurrency-requirements) + the method to be safe to be called concurrently. + +- The method must not interrupt the record processing if the context is canceled + per ["ignoring context cancellation" guideline](../CONTRIBUTING.md#ignoring-context-cancellation). + +- The [specification requires](https://opentelemetry.io/docs/specs/otel/logs/api/#emit-a-logrecord) + use the current time as observed timestamp if the passed is empty. + +- The method should handle the trace context passed via `ctx` argument in order to meet the + [specification's SDK requirement](https://opentelemetry.io/docs/specs/otel/logs/sdk/#readablelogrecord) + to populate the trace context fields from the resolved context. + +`Emit` can be extended by adding new exported fields to the `Record` struct. + +Rejected alternatives: + +- [Record as interface](#record-as-interface) +- [Options as parameter to Logger.Emit](#options-as-parameter-to-loggeremit) +- [Passing record as pointer to Logger.Emit](#passing-record-as-pointer-to-loggeremit) +- [Logger.WithAttributes](#loggerwithattributes) +- [Record attributes as slice](#record-attributes-as-slice) +- [Use any instead of defining Value](#use-any-instead-of-defining-value) +- [Severity type encapsulating number and text](#severity-type-encapsulating-number-and-text) +- [Reuse attribute package](#reuse-attribute-package) +- [Mix receiver types for Record](#mix-receiver-types-for-record) +- [Add XYZ method to Logger](#add-xyz-method-to-logger) +- [Rename KeyValue to Attr](#rename-keyvalue-to-attr) + +### Logger.Enabled + +The `Enabled` method implements the [`Enabled` operation](https://opentelemetry.io/docs/specs/otel/logs/api/#enabled). + +[`Context` associated with the `LogRecord`](https://opentelemetry.io/docs/specs/otel/context/) +is accepted as a `context.Context` method argument. + +Calls to `Enabled` are supposed to be on the hot path and the list of arguments +can be extendend in future. Therefore, in order to reduce the number of heap +allocations and make it possible to handle new arguments, `Enabled` accepts +a `EnabledParameters` struct, defined in [logger.go](logger.go), as the second +method argument. + +The `EnabledParameters` uses fields, instead of getters and setters, to allow +simpler usage which allows configuring the `EnabledParameters` in the same line +where `Enabled` is called. + +### noop package + +The `go.opentelemetry.io/otel/log/noop` package provides +[Logs API No-Op Implementation](https://opentelemetry.io/docs/specs/otel/logs/noop/). + +### Trace context correlation + +The bridge implementation should do its best to pass +the `ctx` containing the trace context from the caller +so it can later be passed via `Logger.Emit`. + +It is not expected that users (caller or bridge implementation) reconstruct +a `context.Context`. Reconstructing a `context.Context` with +[`trace.ContextWithSpanContext`](https://pkg.go.dev/go.opentelemetry.io/otel/trace#ContextWithSpanContext) +and [`trace.NewSpanContext`](https://pkg.go.dev/go.opentelemetry.io/otel/trace#NewSpanContext) +would usually involve more memory allocations. + +The logging libraries which have recording methods that accepts `context.Context`, +such us [`slog`](https://pkg.go.dev/log/slog), +[`logrus`](https://pkg.go.dev/github.com/sirupsen/logrus), +[`zerolog`](https://pkg.go.dev/github.com/rs/zerolog), +makes passing the trace context trivial. + +However, some libraries do not accept a `context.Context` in their recording methods. +Structured logging libraries, +such as [`logr`](https://pkg.go.dev/github.com/go-logr/logr) +and [`zap`](https://pkg.go.dev/go.uber.org/zap), +offer passing `any` type as a log attribute/field. +Therefore, their bridge implementations can define a "special" log attributes/field +that will be used to capture the trace context. + +[The prototype](https://github.com/open-telemetry/opentelemetry-go/pull/4725) +has bridge implementations that handle trace context correlation efficiently. + +## Benchmarking + +The benchmarks take inspiration from [`slog`](https://pkg.go.dev/log/slog), +because for the Go team it was also critical to create API that would be fast +and interoperable with existing logging packages.[^2][^3] + +The benchmark results can be found in [the prototype](https://github.com/open-telemetry/opentelemetry-go/pull/4725). + +## Rejected alternatives + +### Reuse slog + +The API must not be coupled to [`slog`](https://pkg.go.dev/log/slog), +nor any other logging library. + +The API needs to evolve orthogonally to `slog`. + +`slog` is not compliant with the [Logs API](https://opentelemetry.io/docs/specs/otel/logs/api/). +and we cannot expect the Go team to make `slog` compliant with it. + +The interoperability can be achieved using [a log bridge](https://opentelemetry.io/docs/specs/otel/glossary/#log-appender--bridge). + +You can read more about OpenTelemetry Logs design on [opentelemetry.io](https://opentelemetry.io/docs/concepts/signals/logs/). + +### Record as interface + +`Record` is defined as a `struct` because of the following reasons. + +Log record is a value object without any behavior. +It is used as data input for Logger methods. + +The log record resembles the instrument config structs like [metric.Float64CounterConfig](https://pkg.go.dev/go.opentelemetry.io/otel/metric#Float64CounterConfig). + +Using `struct` instead of `interface` improves the performance as e.g. +indirect calls are less optimized, +usage of interfaces tend to increase heap allocations.[^3] + +### Options as parameter to Logger.Emit + +One of the initial ideas was to have: + +```go +type Logger interface{ + embedded.Logger + Emit(ctx context.Context, options ...RecordOption) +} +``` + +The main reason was that design would be similar +to the [Meter API](https://pkg.go.dev/go.opentelemetry.io/otel/metric#Meter) +for creating instruments. + +However, passing `Record` directly, instead of using options, +is more performant as it reduces heap allocations.[^4] + +Another advantage of passing `Record` is that API would not have functions like `NewRecord(options...)`, +which would be used by the SDK and not by the users. + +Finally, the definition would be similar to [`slog.Handler.Handle`](https://pkg.go.dev/log/slog#Handler) +that was designed to provide optimization opportunities.[^2] + +### Passing record as pointer to Logger.Emit + +So far the benchmarks do not show differences that would +favor passing the record via pointer (and vice versa). + +Passing via value feels safer because of the following reasons. + +The user would not be able to pass `nil`. +Therefore, it reduces the possibility to have a nil pointer dereference. + +It should reduce the possibility of a heap allocation. + +It follows the design of [`slog.Handler`](https://pkg.go.dev/log/slog#Handler). + +If follows one of Google's Go Style Decisions +to prefer [passing values](https://google.github.io/styleguide/go/decisions#pass-values). + +### Passing struct as parameter to LoggerProvider.Logger + +Similarly to `Logger.Emit`, we could have something like: + +```go +type LoggerProvider interface{ + embedded.LoggerProvider + Logger(name string, config LoggerConfig) +} +``` + +The drawback of this idea would be that this would be +a different design from Trace and Metrics API. + +The performance of acquiring a logger is not as critical +as the performance of emitting a log record. While a single +HTTP/RPC handler could write hundreds of logs, it should not +create a new logger for each log entry. +The bridge implementation should reuse loggers whenever possible. + +### Logger.WithAttributes + +We could add `WithAttributes` to the `Logger` interface. +Then `Record` could be a simple struct with only exported fields. +The idea was that the SDK would implement the performance improvements +instead of doing it in the API. +This would allow having different optimization strategies. + +During the analysis[^5], it occurred that the main problem of this proposal +is that the variadic slice passed to an interface method is always heap allocated. + +Moreover, the logger returned by `WithAttribute` was allocated on the heap. + +Lastly, the proposal was not specification compliant. + +### Record attributes as slice + +One of the proposals[^6] was to have `Record` as a simple struct: + +```go +type Record struct { + Timestamp time.Time + ObservedTimestamp time.Time + EventName string + Severity Severity + SeverityText string + Body Value + Attributes []KeyValue +} +``` + +The bridge implementations could use [`sync.Pool`](https://pkg.go.dev/sync#Pool) +for reducing the number of allocations when passing attributes. + +The benchmarks results were better. + +In such a design, most bridges would have a `sync.Pool` +to reduce the number of heap allocations. +However, the `sync.Pool` will not work correctly with API implementations +that would take ownership of the record +(e.g. implementations that do not copy records for asynchronous processing). +The current design, even in case of improper API implementation, +has lower chances of encountering a bug as most bridges would +create a record, pass it, and forget about it. + +For reference, here is the reason why `slog` does not use `sync.Pool`[^3] +as well: + +> We can use a sync pool for records though we decided not to. +You can but it's a bad idea for us. Why? +Because users have control of Records. +Handler writers can get their hands on a record +and we'd have to ask them to free it +or try to free it magically at some some point. +But either way, they could get themselves in trouble by freeing it twice +or holding on to one after they free it. +That's a use after free bug and that's why `zerolog` was problematic for us. +`zerolog` as as part of its speed exposes a pool allocated value to users +if you use `zerolog` the normal way, that you'll see in all the examples, +you will never encounter a problem. +But if you do something a little out of the ordinary you can get +use after free bugs and we just didn't want to put that in the standard library. + +Therefore, we decided to not follow the proposal as it is +less user friendly (users and bridges would use e.g. a `sync.Pool` to reduce +the number of heap allocation), less safe (more prone to use after free bugs +and race conditions), and the benchmark differences were not significant. + +### Use any instead of defining Value + +[Logs Data Model](https://opentelemetry.io/docs/specs/otel/logs/data-model/#field-body) +defines Body to be `any`. +One could propose to define `Body` (and attribute values) as `any` +instead of a defining a new type (`Value`). + +First of all, [`any` type defined in the specification](https://opentelemetry.io/docs/specs/otel/logs/data-model/#type-any) +is not the same as `any` (`interface{}`) in Go. + +Moreover, using `any` as a field would decrease the performance.[^7] + +Notice it will be still possible to add following kind and factories +in a backwards compatible way: + +```go +const KindMap Kind + +func AnyValue(value any) KeyValue + +func Any(key string, value any) KeyValue +``` + +However, currently, it would not be specification compliant. + +### Severity type encapsulating number and text + +We could combine severity into a single field defining a type: + +```go +type Severity struct { + Number SeverityNumber + Text string +} +``` + +However, the [Logs Data Model](https://opentelemetry.io/docs/specs/otel/logs/data-model/#log-and-event-record-definition) +define it as independent fields. +It should be more user friendly to have them separated. +Especially when having getter and setter methods, setting one value +when the other is already set would be unpleasant. + +### Reuse attribute package + +It was tempting to reuse the existing +[https://pkg.go.dev/go.opentelemetry.io/otel/attribute] package +for defining log attributes and body. + +However, this would be wrong because [the log attribute definition](https://opentelemetry.io/docs/specs/otel/logs/data-model/#field-attributes) +is different from [the common attribute definition](https://opentelemetry.io/docs/specs/otel/common/#attribute). + +Moreover, it there is nothing telling that [the body definition](https://opentelemetry.io/docs/specs/otel/logs/data-model/#field-body) +has anything in common with a common attribute value. + +Therefore, we define new types representing the abstract types defined +in the [Logs Data Model](https://opentelemetry.io/docs/specs/otel/logs/data-model/#definitions-used-in-this-document). + +### Mix receiver types for Record + +Methods of [`slog.Record`](https://pkg.go.dev/log/slog#Record) +have different receiver types. + +In `log/slog` GitHub issue we can only find that the reason is:[^8] + +>> some receiver of Record struct is by value +> Passing Records by value means they incur no heap allocation. +> That improves performance overall, even though they are copied. + +However, the benchmarks do not show any noticeable differences.[^9] + +The compiler is smart-enough to not make a heap allocation for any of these methods. +The use of a pointer receiver does not cause any heap allocation. +From Go FAQ:[^10] + +> In the current compilers, if a variable has its address taken, +> that variable is a candidate for allocation on the heap. +> However, a basic escape analysis recognizes some cases +> when such variables will not live past the return from the function +> and can reside on the stack. + +The [Understanding Allocations: the Stack and the Heap](https://www.youtube.com/watch?v=ZMZpH4yT7M0) +presentation by Jacob Walker describes the escape analysis with details. + +Moreover, also from Go FAQ:[^10] + +> Also, if a local variable is very large, +> it might make more sense to store it on the heap rather than the stack. + +Therefore, even if we use a value receiver and the value is very large +it may be heap allocated. + +Both [Go Code Review Comments](https://go.dev/wiki/CodeReviewComments#receiver-type) +and [Google's Go Style Decisions](https://google.github.io/styleguide/go/decisions#receiver-type) +highly recommend making the methods for a type either all pointer methods +or all value methods. Google's Go Style Decisions even goes further and says: + +> There is a lot of misinformation about whether passing a value or a pointer +> to a function can affect performance. +> The compiler can choose to pass pointers to values on the stack +> as well as copying values on the stack, +> but these considerations should not outweigh the readability +> and correctness of the code in most circumstances. +> When the performance does matter, it is important to profile both approaches +> with a realistic benchmark before deciding that one approach outperforms the other. + +Because, the benchmarks[^9] do not proof any performance difference +and the general recommendation is to not mix receiver types, +we decided to use pointer receivers for all `Record` methods. + +### Add XYZ method to Logger + +The `Logger` does not have methods like `SetSeverity`, etc. +as the Logs API needs to follow (be compliant with) +the [specification](https://opentelemetry.io/docs/specs/otel/logs/api/) + +### Rename KeyValue to Attr + +There was a proposal to rename `KeyValue` to `Attr` (or `Attribute`).[^11] +New developers may not intuitively know that `log.KeyValue` is an attribute in +the OpenTelemetry parlance. + +During the discussion we agreed to keep the `KeyValue` name. + +The type is used in multiple semantics: + +- as a log attribute, +- as a map item, +- as a log record Body. + +As for map item semantics, this type is a key-value pair, not an attribute. +Naming the type as `Attr` would convey semantical meaning +that would not be correct for a map. + +We expect that most of the Logs API users will be OpenTelemetry contributors. +We plan to implement bridges for the most popular logging libraries ourselves. +Given we will all have the context needed to disambiguate these overlapping +names, developers' confusion should not be an issue. + +For bridges not developed by us, +developers will likely look at our existing bridges for inspiration. +Our correct use of these types will be a reference to them. + +At last, we provide `ValueFromAttribute` and `KeyValueFromAttribute` +to offer reuse of `attribute.Value` and `attribute.KeyValue`. + +[^1]: [Handle structured body and attributes](https://github.com/pellared/opentelemetry-go/pull/7) +[^2]: Jonathan Amsterdam, [The Go Blog: Structured Logging with slog](https://go.dev/blog/slog) +[^3]: Jonathan Amsterdam, [GopherCon Europe 2023: A Fast Structured Logging Package](https://www.youtube.com/watch?v=tC4Jt3i62ns) +[^4]: [Emit definition discussion with benchmarks](https://github.com/open-telemetry/opentelemetry-go/pull/4725#discussion_r1400869566) +[^5]: [Logger.WithAttributes analysis](https://github.com/pellared/opentelemetry-go/pull/3) +[^6]: [Record attributes as field and use sync.Pool for reducing allocations](https://github.com/pellared/opentelemetry-go/pull/4) and [Record attributes based on slog.Record](https://github.com/pellared/opentelemetry-go/pull/6) +[^7]: [Record.Body as any](https://github.com/pellared/opentelemetry-go/pull/5) +[^8]: [log/slog: structured, leveled logging](https://github.com/golang/go/issues/56345#issuecomment-1302563756) +[^9]: [Record with pointer receivers only](https://github.com/pellared/opentelemetry-go/pull/8) +[^10]: [Go FAQ: Stack or heap](https://go.dev/doc/faq#stack_or_heap) +[^11]: [Rename KeyValue to Attr discussion](https://github.com/open-telemetry/opentelemetry-go/pull/4809#discussion_r1476080093) diff --git a/vendor/go.opentelemetry.io/otel/log/LICENSE b/vendor/go.opentelemetry.io/otel/log/LICENSE new file mode 100644 index 0000000000..f1aee0f110 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/log/LICENSE @@ -0,0 +1,231 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +-------------------------------------------------------------------------------- + +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/vendor/go.opentelemetry.io/otel/log/README.md b/vendor/go.opentelemetry.io/otel/log/README.md new file mode 100644 index 0000000000..3f71427119 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/log/README.md @@ -0,0 +1,3 @@ +# Log API + +[![PkgGoDev](https://pkg.go.dev/badge/go.opentelemetry.io/otel/log)](https://pkg.go.dev/go.opentelemetry.io/otel/log) diff --git a/vendor/go.opentelemetry.io/otel/log/doc.go b/vendor/go.opentelemetry.io/otel/log/doc.go new file mode 100644 index 0000000000..b7a085c63d --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/log/doc.go @@ -0,0 +1,85 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +/* +Package log provides the OpenTelemetry Logs API. + +This API is separate from its implementation so the instrumentation built from +it is reusable. See [go.opentelemetry.io/otel/sdk/log] for the official +OpenTelemetry implementation of this API. + +The log package provides the OpenTelemetry Logs API, which serves as a standard +interface for generating and managing log records within the OpenTelemetry ecosystem. +This package allows users to emit LogRecords, enabling structured, context-rich logging +that can be easily integrated with observability tools. It ensures that log data is captured +in a way that is consistent with OpenTelemetry's data model. + +This package can be used to create bridges between existing logging libraries and OpenTelemetry. +Log bridges allow integrating the existing logging setups with OpenTelemetry. +Log bridges can be found in the [registry]. + +# API Implementations + +This package does not conform to the standard Go versioning policy, all of its +interfaces may have methods added to them without a package major version bump. +This non-standard API evolution could surprise an uninformed implementation +author. They could unknowingly build their implementation in a way that would +result in a runtime panic for their users that update to the new API. + +The API is designed to help inform an instrumentation author about this +non-standard API evolution. It requires them to choose a default behavior for +unimplemented interface methods. There are three behavior choices they can +make: + + - Compilation failure + - Panic + - Default to another implementation + +All interfaces in this API embed a corresponding interface from +[go.opentelemetry.io/otel/log/embedded]. If an author wants the default +behavior of their implementations to be a compilation failure, signaling to +their users they need to update to the latest version of that implementation, +they need to embed the corresponding interface from +[go.opentelemetry.io/otel/log/embedded] in their implementation. For example, + + import "go.opentelemetry.io/otel/log/embedded" + + type LoggerProvider struct { + embedded.LoggerProvider + // ... + } + +If an author wants the default behavior of their implementations to a panic, +they need to embed the API interface directly. + + import "go.opentelemetry.io/otel/log" + + type LoggerProvider struct { + log.LoggerProvider + // ... + } + +This is not a recommended behavior as it could lead to publishing packages that +contain runtime panics when users update other package that use newer versions +of [go.opentelemetry.io/otel/log]. + +Finally, an author can embed another implementation in theirs. The embedded +implementation will be used for methods not defined by the author. For example, +an author who wants to default to silently dropping the call can use +[go.opentelemetry.io/otel/log/noop]: + + import "go.opentelemetry.io/otel/log/noop" + + type LoggerProvider struct { + noop.LoggerProvider + // ... + } + +It is strongly recommended that authors only embed +go.opentelemetry.io/otel/log/noop if they choose this default behavior. That +implementation is the only one OpenTelemetry authors can guarantee will fully +implement all the API interfaces when a user updates their API. + +[registry]: https://opentelemetry.io/ecosystem/registry/?language=go&component=log-bridge +*/ +package log // import "go.opentelemetry.io/otel/log" diff --git a/vendor/go.opentelemetry.io/otel/log/embedded/README.md b/vendor/go.opentelemetry.io/otel/log/embedded/README.md new file mode 100644 index 0000000000..bae4ac68f0 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/log/embedded/README.md @@ -0,0 +1,3 @@ +# Log Embedded + +[![PkgGoDev](https://pkg.go.dev/badge/go.opentelemetry.io/otel/log/embedded)](https://pkg.go.dev/go.opentelemetry.io/otel/log/embedded) diff --git a/vendor/go.opentelemetry.io/otel/log/embedded/embedded.go b/vendor/go.opentelemetry.io/otel/log/embedded/embedded.go new file mode 100644 index 0000000000..9b401b2b17 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/log/embedded/embedded.go @@ -0,0 +1,36 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// Package embedded provides interfaces embedded within the [OpenTelemetry Logs +// Bridge API]. +// +// Implementers of the [OpenTelemetry Logs API] can embed the relevant +// type from this package into their implementation directly. Doing so will +// result in a compilation error for users when the [OpenTelemetry Logs Bridge +// API] is extended (which is something that can happen without a major version +// bump of the API package). +// +// [OpenTelemetry Logs API]: https://pkg.go.dev/go.opentelemetry.io/otel/log +package embedded // import "go.opentelemetry.io/otel/log/embedded" + +// LoggerProvider is embedded in the [Logs API LoggerProvider]. +// +// Embed this interface in your implementation of the [Logs API +// LoggerProvider] if you want users to experience a compilation error, +// signaling they need to update to your latest implementation, when the [Logs +// Bridge API LoggerProvider] interface is extended (which is something that +// can happen without a major version bump of the API package). +// +// [Logs API LoggerProvider]: https://pkg.go.dev/go.opentelemetry.io/otel/log#LoggerProvider +type LoggerProvider interface{ loggerProvider() } + +// Logger is embedded in [Logs API Logger]. +// +// Embed this interface in your implementation of the [Logs API Logger] +// if you want users to experience a compilation error, signaling they need to +// update to your latest implementation, when the [Logs API Logger] +// interface is extended (which is something that can happen without a major +// version bump of the API package). +// +// [Logs API Logger]: https://pkg.go.dev/go.opentelemetry.io/otel/log#Logger +type Logger interface{ logger() } diff --git a/vendor/go.opentelemetry.io/otel/log/global/README.md b/vendor/go.opentelemetry.io/otel/log/global/README.md new file mode 100644 index 0000000000..11e5afefc0 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/log/global/README.md @@ -0,0 +1,3 @@ +# Log Global + +[![PkgGoDev](https://pkg.go.dev/badge/go.opentelemetry.io/otel/log/global)](https://pkg.go.dev/go.opentelemetry.io/otel/log/global) diff --git a/vendor/go.opentelemetry.io/otel/log/global/log.go b/vendor/go.opentelemetry.io/otel/log/global/log.go new file mode 100644 index 0000000000..bfdb184790 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/log/global/log.go @@ -0,0 +1,49 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +/* +Package global provides access to a global implementation of the OpenTelemetry +Logs API. + +This package is experimental. It will be deprecated and removed when the [log] +package becomes stable. Its functionality will be migrated to +go.opentelemetry.io/otel. +*/ +package global // import "go.opentelemetry.io/otel/log/global" + +import ( + "go.opentelemetry.io/otel/log" + "go.opentelemetry.io/otel/log/internal/global" +) + +// Logger returns a [log.Logger] configured with the provided name and options +// from the globally configured [log.LoggerProvider]. +// +// If this is called before a global LoggerProvider is configured, the returned +// Logger will be a No-Op implementation of a Logger. When a global +// LoggerProvider is registered for the first time, the returned Logger is +// updated in-place to report to this new LoggerProvider. There is no need to +// call this function again for an updated instance. +// +// This is a convenience function. It is equivalent to: +// +// GetLoggerProvider().Logger(name, options...) +func Logger(name string, options ...log.LoggerOption) log.Logger { + return GetLoggerProvider().Logger(name, options...) +} + +// GetLoggerProvider returns the globally configured [log.LoggerProvider]. +// +// If a global LoggerProvider has not been configured with [SetLoggerProvider], +// the returned Logger will be a No-Op implementation of a LoggerProvider. When +// a global LoggerProvider is registered for the first time, the returned +// LoggerProvider and all of its created Loggers are updated in-place. There is +// no need to call this function again for an updated instance. +func GetLoggerProvider() log.LoggerProvider { + return global.GetLoggerProvider() +} + +// SetLoggerProvider configures provider as the global [log.LoggerProvider]. +func SetLoggerProvider(provider log.LoggerProvider) { + global.SetLoggerProvider(provider) +} diff --git a/vendor/go.opentelemetry.io/otel/log/internal/global/log.go b/vendor/go.opentelemetry.io/otel/log/internal/global/log.go new file mode 100644 index 0000000000..e463acbf19 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/log/internal/global/log.go @@ -0,0 +1,109 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// Package global is the internal implementation of the OpenTelemetry global +// Logs API. +package global // import "go.opentelemetry.io/otel/log/internal/global" + +import ( + "context" + "sync" + "sync/atomic" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/log" + "go.opentelemetry.io/otel/log/embedded" +) + +// instLib defines the instrumentation library a logger is created for. +// +// Do not use sdk/instrumentation (API cannot depend on the SDK). +type instLib struct { + name string + version string + schemaURL string + attrs attribute.Set +} + +type loggerProvider struct { + embedded.LoggerProvider + + mu sync.Mutex + loggers map[instLib]*logger + delegate log.LoggerProvider +} + +// Compile-time guarantee loggerProvider implements LoggerProvider. +var _ log.LoggerProvider = (*loggerProvider)(nil) + +func (p *loggerProvider) Logger(name string, options ...log.LoggerOption) log.Logger { + p.mu.Lock() + defer p.mu.Unlock() + + if p.delegate != nil { + return p.delegate.Logger(name, options...) + } + + cfg := log.NewLoggerConfig(options...) + key := instLib{ + name: name, + version: cfg.InstrumentationVersion(), + schemaURL: cfg.SchemaURL(), + attrs: cfg.InstrumentationAttributes(), + } + + if p.loggers == nil { + l := &logger{name: name, options: options} + p.loggers = map[instLib]*logger{key: l} + return l + } + + if l, ok := p.loggers[key]; ok { + return l + } + + l := &logger{name: name, options: options} + p.loggers[key] = l + return l +} + +func (p *loggerProvider) setDelegate(provider log.LoggerProvider) { + p.mu.Lock() + defer p.mu.Unlock() + + p.delegate = provider + for _, l := range p.loggers { + l.setDelegate(provider) + } + p.loggers = nil // Only set logger delegates once. +} + +type logger struct { + embedded.Logger + + name string + options []log.LoggerOption + + delegate atomic.Value // log.Logger +} + +// Compile-time guarantee logger implements Logger. +var _ log.Logger = (*logger)(nil) + +func (l *logger) Emit(ctx context.Context, r log.Record) { + if del, ok := l.delegate.Load().(log.Logger); ok { + del.Emit(ctx, r) + } +} + +func (l *logger) Enabled(ctx context.Context, param log.EnabledParameters) bool { + var enabled bool + if del, ok := l.delegate.Load().(log.Logger); ok { + enabled = del.Enabled(ctx, param) + } + return enabled +} + +func (l *logger) setDelegate(provider log.LoggerProvider) { + l.delegate.Store(provider.Logger(l.name, l.options...)) +} diff --git a/vendor/go.opentelemetry.io/otel/log/internal/global/state.go b/vendor/go.opentelemetry.io/otel/log/internal/global/state.go new file mode 100644 index 0000000000..dbe1c2fbfb --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/log/internal/global/state.go @@ -0,0 +1,53 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package global // import "go.opentelemetry.io/otel/log/internal/global" + +import ( + "errors" + "sync" + "sync/atomic" + + "go.opentelemetry.io/otel/internal/global" + "go.opentelemetry.io/otel/log" +) + +var ( + globalLoggerProvider = defaultLoggerProvider() + + delegateLoggerOnce sync.Once +) + +func defaultLoggerProvider() *atomic.Value { + v := &atomic.Value{} + v.Store(loggerProviderHolder{provider: &loggerProvider{}}) + return v +} + +type loggerProviderHolder struct { + provider log.LoggerProvider +} + +// GetLoggerProvider returns the global LoggerProvider. +func GetLoggerProvider() log.LoggerProvider { + return globalLoggerProvider.Load().(loggerProviderHolder).provider +} + +// SetLoggerProvider sets the global LoggerProvider. +func SetLoggerProvider(provider log.LoggerProvider) { + current := GetLoggerProvider() + if _, cOk := current.(*loggerProvider); cOk { + if _, mpOk := provider.(*loggerProvider); mpOk && current == provider { + err := errors.New("invalid delegation: LoggerProvider self-delegation") + global.Error(err, "No delegate will be configured") + return + } + } + + delegateLoggerOnce.Do(func() { + if def, ok := current.(*loggerProvider); ok { + def.setDelegate(provider) + } + }) + globalLoggerProvider.Store(loggerProviderHolder{provider: provider}) +} diff --git a/vendor/go.opentelemetry.io/otel/log/keyvalue.go b/vendor/go.opentelemetry.io/otel/log/keyvalue.go new file mode 100644 index 0000000000..ebf4fcbee9 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/log/keyvalue.go @@ -0,0 +1,453 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +//go:generate stringer -type=Kind -trimprefix=Kind + +package log // import "go.opentelemetry.io/otel/log" + +import ( + "bytes" + "cmp" + "errors" + "fmt" + "math" + "slices" + "strconv" + "unsafe" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/internal/global" +) + +// errKind is logged when a Value is decoded to an incompatible type. +var errKind = errors.New("invalid Kind") + +// Kind is the kind of a [Value]. +type Kind int + +// Kind values. +const ( + KindEmpty Kind = iota + KindBool + KindFloat64 + KindInt64 + KindString + KindBytes + KindSlice + KindMap +) + +// A Value represents a structured log value. +// A zero value is valid and represents an empty value. +type Value struct { + // Ensure forward compatibility by explicitly making this not comparable. + noCmp [0]func() //nolint: unused // This is indeed used. + + // num holds the value for Int64, Float64, and Bool. It holds the length + // for String, Bytes, Slice, Map. + num uint64 + // any holds either the KindBool, KindInt64, KindFloat64, stringptr, + // bytesptr, sliceptr, or mapptr. If KindBool, KindInt64, or KindFloat64 + // then the value of Value is in num as described above. Otherwise, it + // contains the value wrapped in the appropriate type. + any any +} + +type ( + // sliceptr represents a value in Value.any for KindString Values. + stringptr *byte + // bytesptr represents a value in Value.any for KindBytes Values. + bytesptr *byte + // sliceptr represents a value in Value.any for KindSlice Values. + sliceptr *Value + // mapptr represents a value in Value.any for KindMap Values. + mapptr *KeyValue +) + +// StringValue returns a new [Value] for a string. +func StringValue(v string) Value { + return Value{ + num: uint64(len(v)), + any: stringptr(unsafe.StringData(v)), + } +} + +// IntValue returns a [Value] for an int. +func IntValue(v int) Value { return Int64Value(int64(v)) } + +// Int64Value returns a [Value] for an int64. +func Int64Value(v int64) Value { + // This can be later converted back to int64 (overflow not checked). + return Value{num: uint64(v), any: KindInt64} // nolint:gosec +} + +// Float64Value returns a [Value] for a float64. +func Float64Value(v float64) Value { + return Value{num: math.Float64bits(v), any: KindFloat64} +} + +// BoolValue returns a [Value] for a bool. +func BoolValue(v bool) Value { //nolint:revive // Not a control flag. + var n uint64 + if v { + n = 1 + } + return Value{num: n, any: KindBool} +} + +// BytesValue returns a [Value] for a byte slice. The passed slice must not be +// changed after it is passed. +func BytesValue(v []byte) Value { + return Value{ + num: uint64(len(v)), + any: bytesptr(unsafe.SliceData(v)), + } +} + +// SliceValue returns a [Value] for a slice of [Value]. The passed slice must +// not be changed after it is passed. +func SliceValue(vs ...Value) Value { + return Value{ + num: uint64(len(vs)), + any: sliceptr(unsafe.SliceData(vs)), + } +} + +// MapValue returns a new [Value] for a slice of key-value pairs. The passed +// slice must not be changed after it is passed. +func MapValue(kvs ...KeyValue) Value { + return Value{ + num: uint64(len(kvs)), + any: mapptr(unsafe.SliceData(kvs)), + } +} + +// AsString returns the value held by v as a string. +func (v Value) AsString() string { + if sp, ok := v.any.(stringptr); ok { + return unsafe.String(sp, v.num) + } + global.Error(errKind, "AsString", "Kind", v.Kind()) + return "" +} + +// asString returns the value held by v as a string. It will panic if the Value +// is not KindString. +func (v Value) asString() string { + return unsafe.String(v.any.(stringptr), v.num) +} + +// AsInt64 returns the value held by v as an int64. +func (v Value) AsInt64() int64 { + if v.Kind() != KindInt64 { + global.Error(errKind, "AsInt64", "Kind", v.Kind()) + return 0 + } + return v.asInt64() +} + +// asInt64 returns the value held by v as an int64. If v is not of KindInt64, +// this will return garbage. +func (v Value) asInt64() int64 { + // Assumes v.num was a valid int64 (overflow not checked). + return int64(v.num) // nolint: gosec +} + +// AsBool returns the value held by v as a bool. +func (v Value) AsBool() bool { + if v.Kind() != KindBool { + global.Error(errKind, "AsBool", "Kind", v.Kind()) + return false + } + return v.asBool() +} + +// asBool returns the value held by v as a bool. If v is not of KindBool, this +// will return garbage. +func (v Value) asBool() bool { return v.num == 1 } + +// AsFloat64 returns the value held by v as a float64. +func (v Value) AsFloat64() float64 { + if v.Kind() != KindFloat64 { + global.Error(errKind, "AsFloat64", "Kind", v.Kind()) + return 0 + } + return v.asFloat64() +} + +// asFloat64 returns the value held by v as a float64. If v is not of +// KindFloat64, this will return garbage. +func (v Value) asFloat64() float64 { return math.Float64frombits(v.num) } + +// AsBytes returns the value held by v as a []byte. +func (v Value) AsBytes() []byte { + if sp, ok := v.any.(bytesptr); ok { + return unsafe.Slice((*byte)(sp), v.num) + } + global.Error(errKind, "AsBytes", "Kind", v.Kind()) + return nil +} + +// asBytes returns the value held by v as a []byte. It will panic if the Value +// is not KindBytes. +func (v Value) asBytes() []byte { + return unsafe.Slice((*byte)(v.any.(bytesptr)), v.num) +} + +// AsSlice returns the value held by v as a []Value. +func (v Value) AsSlice() []Value { + if sp, ok := v.any.(sliceptr); ok { + return unsafe.Slice((*Value)(sp), v.num) + } + global.Error(errKind, "AsSlice", "Kind", v.Kind()) + return nil +} + +// asSlice returns the value held by v as a []Value. It will panic if the Value +// is not KindSlice. +func (v Value) asSlice() []Value { + return unsafe.Slice((*Value)(v.any.(sliceptr)), v.num) +} + +// AsMap returns the value held by v as a []KeyValue. +func (v Value) AsMap() []KeyValue { + if sp, ok := v.any.(mapptr); ok { + return unsafe.Slice((*KeyValue)(sp), v.num) + } + global.Error(errKind, "AsMap", "Kind", v.Kind()) + return nil +} + +// asMap returns the value held by v as a []KeyValue. It will panic if the +// Value is not KindMap. +func (v Value) asMap() []KeyValue { + return unsafe.Slice((*KeyValue)(v.any.(mapptr)), v.num) +} + +// Kind returns the Kind of v. +func (v Value) Kind() Kind { + switch x := v.any.(type) { + case Kind: + return x + case stringptr: + return KindString + case bytesptr: + return KindBytes + case sliceptr: + return KindSlice + case mapptr: + return KindMap + default: + return KindEmpty + } +} + +// Empty reports whether v does not hold any value. +func (v Value) Empty() bool { return v.Kind() == KindEmpty } + +// Equal reports whether v is equal to w. +func (v Value) Equal(w Value) bool { + k1 := v.Kind() + k2 := w.Kind() + if k1 != k2 { + return false + } + switch k1 { + case KindInt64, KindBool: + return v.num == w.num + case KindString: + return v.asString() == w.asString() + case KindFloat64: + return v.asFloat64() == w.asFloat64() + case KindSlice: + return slices.EqualFunc(v.asSlice(), w.asSlice(), Value.Equal) + case KindMap: + sv := sortMap(v.asMap()) + sw := sortMap(w.asMap()) + return slices.EqualFunc(sv, sw, KeyValue.Equal) + case KindBytes: + return bytes.Equal(v.asBytes(), w.asBytes()) + case KindEmpty: + return true + default: + global.Error(errKind, "Equal", "Kind", k1) + return false + } +} + +func sortMap(m []KeyValue) []KeyValue { + sm := make([]KeyValue, len(m)) + copy(sm, m) + slices.SortFunc(sm, func(a, b KeyValue) int { + return cmp.Compare(a.Key, b.Key) + }) + + return sm +} + +// String returns Value's value as a string, formatted like [fmt.Sprint]. +// +// The returned string is meant for debugging; +// the string representation is not stable. +func (v Value) String() string { + switch v.Kind() { + case KindString: + return v.asString() + case KindInt64: + // Assumes v.num was a valid int64 (overflow not checked). + return strconv.FormatInt(int64(v.num), 10) // nolint: gosec + case KindFloat64: + return strconv.FormatFloat(v.asFloat64(), 'g', -1, 64) + case KindBool: + return strconv.FormatBool(v.asBool()) + case KindBytes: + return fmt.Sprint(v.asBytes()) // nolint:staticcheck // Use fmt.Sprint to encode as slice. + case KindMap: + return fmt.Sprint(v.asMap()) + case KindSlice: + return fmt.Sprint(v.asSlice()) + case KindEmpty: + return "" + default: + // Try to handle this as gracefully as possible. + // + // Don't panic here. The goal here is to have developers find this + // first if a slog.Kind is is not handled. It is + // preferable to have user's open issue asking why their attributes + // have a "unhandled: " prefix than say that their code is panicking. + return fmt.Sprintf("", v.Kind()) + } +} + +// A KeyValue is a key-value pair used to represent a log attribute (a +// superset of [go.opentelemetry.io/otel/attribute.KeyValue]) and map item. +type KeyValue struct { + Key string + Value Value +} + +// Equal reports whether a is equal to b. +func (a KeyValue) Equal(b KeyValue) bool { + return a.Key == b.Key && a.Value.Equal(b.Value) +} + +// String returns a KeyValue for a string value. +func String(key, value string) KeyValue { + return KeyValue{key, StringValue(value)} +} + +// Int64 returns a KeyValue for an int64 value. +func Int64(key string, value int64) KeyValue { + return KeyValue{key, Int64Value(value)} +} + +// Int returns a KeyValue for an int value. +func Int(key string, value int) KeyValue { + return KeyValue{key, IntValue(value)} +} + +// Float64 returns a KeyValue for a float64 value. +func Float64(key string, value float64) KeyValue { + return KeyValue{key, Float64Value(value)} +} + +// Bool returns a KeyValue for a bool value. +func Bool(key string, value bool) KeyValue { + return KeyValue{key, BoolValue(value)} +} + +// Bytes returns a KeyValue for a []byte value. +// The passed slice must not be changed after it is passed. +func Bytes(key string, value []byte) KeyValue { + return KeyValue{key, BytesValue(value)} +} + +// Slice returns a KeyValue for a []Value value. +// The passed slice must not be changed after it is passed. +func Slice(key string, value ...Value) KeyValue { + return KeyValue{key, SliceValue(value...)} +} + +// Map returns a KeyValue for a map value. +// The passed slice must not be changed after it is passed. +func Map(key string, value ...KeyValue) KeyValue { + return KeyValue{key, MapValue(value...)} +} + +// Empty returns a KeyValue with an empty value. +func Empty(key string) KeyValue { + return KeyValue{key, Value{}} +} + +// String returns key-value pair as a string, formatted like "key:value". +// +// The returned string is meant for debugging; +// the string representation is not stable. +func (a KeyValue) String() string { + return fmt.Sprintf("%s:%s", a.Key, a.Value) +} + +// ValueFromAttribute converts [attribute.Value] to [Value]. +func ValueFromAttribute(value attribute.Value) Value { + switch value.Type() { + case attribute.EMPTY: + return Value{} + case attribute.BOOL: + return BoolValue(value.AsBool()) + case attribute.BOOLSLICE: + val := value.AsBoolSlice() + res := make([]Value, 0, len(val)) + for _, v := range val { + res = append(res, BoolValue(v)) + } + return SliceValue(res...) + case attribute.INT64: + return Int64Value(value.AsInt64()) + case attribute.INT64SLICE: + val := value.AsInt64Slice() + res := make([]Value, 0, len(val)) + for _, v := range val { + res = append(res, Int64Value(v)) + } + return SliceValue(res...) + case attribute.FLOAT64: + return Float64Value(value.AsFloat64()) + case attribute.FLOAT64SLICE: + val := value.AsFloat64Slice() + res := make([]Value, 0, len(val)) + for _, v := range val { + res = append(res, Float64Value(v)) + } + return SliceValue(res...) + case attribute.STRING: + return StringValue(value.AsString()) + case attribute.STRINGSLICE: + val := value.AsStringSlice() + res := make([]Value, 0, len(val)) + for _, v := range val { + res = append(res, StringValue(v)) + } + return SliceValue(res...) + case attribute.BYTESLICE: + val := value.AsByteSlice() + return BytesValue(val) + case attribute.SLICE: + val := value.AsSlice() + res := make([]Value, 0, len(val)) + for _, v := range val { + res = append(res, ValueFromAttribute(v)) + } + return SliceValue(res...) + } + // This code should never be reached + // as log attributes are a superset of standard attributes. + panic("unknown attribute type") +} + +// KeyValueFromAttribute converts [attribute.KeyValue] to [KeyValue]. +func KeyValueFromAttribute(kv attribute.KeyValue) KeyValue { + return KeyValue{ + Key: string(kv.Key), + Value: ValueFromAttribute(kv.Value), + } +} diff --git a/vendor/go.opentelemetry.io/otel/log/kind_string.go b/vendor/go.opentelemetry.io/otel/log/kind_string.go new file mode 100644 index 0000000000..b4f9e53354 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/log/kind_string.go @@ -0,0 +1,31 @@ +// Code generated by "stringer -type=Kind -trimprefix=Kind"; DO NOT EDIT. + +package log + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[KindEmpty-0] + _ = x[KindBool-1] + _ = x[KindFloat64-2] + _ = x[KindInt64-3] + _ = x[KindString-4] + _ = x[KindBytes-5] + _ = x[KindSlice-6] + _ = x[KindMap-7] +} + +const _Kind_name = "EmptyBoolFloat64Int64StringBytesSliceMap" + +var _Kind_index = [...]uint8{0, 5, 9, 16, 21, 27, 32, 37, 40} + +func (i Kind) String() string { + idx := int(i) - 0 + if i < 0 || idx >= len(_Kind_index)-1 { + return "Kind(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Kind_name[_Kind_index[idx]:_Kind_index[idx+1]] +} diff --git a/vendor/go.opentelemetry.io/otel/log/logger.go b/vendor/go.opentelemetry.io/otel/log/logger.go new file mode 100644 index 0000000000..014161969b --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/log/logger.go @@ -0,0 +1,188 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package log // import "go.opentelemetry.io/otel/log" + +import ( + "context" + "slices" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/log/embedded" +) + +// Logger emits log records. +// +// Warning: Methods may be added to this interface in minor releases. See +// package documentation on API implementation for information on how to set +// default behavior for unimplemented methods. +type Logger interface { + // Users of the interface can ignore this. This embedded type is only used + // by implementations of this interface. See the "API Implementations" + // section of the package documentation for more information. + embedded.Logger + + // Emit emits a log record. + // + // The record may be held by the implementation. Callers should not mutate + // the record after passed. + // + // Implementations of this method need to be safe for a user to call + // concurrently. + Emit(ctx context.Context, record Record) + + // Enabled reports whether the Logger emits for the given context and + // param. + // + // This is useful for users that want to know if a [Record] + // will be processed or dropped before they perform complex operations to + // construct the [Record]. + // + // The passed param is likely to be a partial record information being + // provided (e.g a param with only the Severity set). + // If a Logger needs more information than is provided, it + // is said to be in an indeterminate state (see below). + // + // The returned value will be true when the Logger will emit for the + // provided context and param, and will be false if the Logger will not + // emit. The returned value may be true or false in an indeterminate state. + // An implementation should default to returning true for an indeterminate + // state, but may return false if valid reasons in particular circumstances + // exist (e.g. performance, correctness). + // + // The param should not be held by the implementation. A copy should be + // made if the param needs to be held after the call returns. + // + // Implementations of this method need to be safe for a user to call + // concurrently. + Enabled(ctx context.Context, param EnabledParameters) bool +} + +// LoggerOption applies configuration options to a [Logger]. +type LoggerOption interface { + // applyLogger is used to set a LoggerOption value of a LoggerConfig. + applyLogger(LoggerConfig) LoggerConfig +} + +// LoggerConfig contains options for a [Logger]. +type LoggerConfig struct { + // Ensure forward compatibility by explicitly making this not comparable. + noCmp [0]func() //nolint: unused // This is indeed used. + + version string + schemaURL string + attrs attribute.Set +} + +type experimentalOption interface { + Experimental() +} + +// NewLoggerConfig returns a new [LoggerConfig] with all the options applied. +func NewLoggerConfig(options ...LoggerOption) LoggerConfig { + var c LoggerConfig + for _, opt := range options { + if _, ok := opt.(experimentalOption); ok { + continue + } + c = opt.applyLogger(c) + } + return c +} + +// InstrumentationVersion returns the version of the library providing +// instrumentation. +func (cfg LoggerConfig) InstrumentationVersion() string { + return cfg.version +} + +// InstrumentationAttributes returns the attributes associated with the library +// providing instrumentation. +func (cfg LoggerConfig) InstrumentationAttributes() attribute.Set { + return cfg.attrs +} + +// SchemaURL returns the schema URL of the library providing instrumentation. +func (cfg LoggerConfig) SchemaURL() string { + return cfg.schemaURL +} + +type loggerOptionFunc func(LoggerConfig) LoggerConfig + +func (fn loggerOptionFunc) applyLogger(cfg LoggerConfig) LoggerConfig { + return fn(cfg) +} + +// WithInstrumentationVersion returns a [LoggerOption] that sets the +// instrumentation version of a [Logger]. +func WithInstrumentationVersion(version string) LoggerOption { + return loggerOptionFunc(func(config LoggerConfig) LoggerConfig { + config.version = version + return config + }) +} + +// mergeSets returns the union of keys between a and b. Any duplicate keys will +// use the value associated with b. +func mergeSets(a, b attribute.Set) attribute.Set { + // NewMergeIterator uses the first value for any duplicates. + iter := attribute.NewMergeIterator(&b, &a) + merged := make([]attribute.KeyValue, 0, a.Len()+b.Len()) + for iter.Next() { + merged = append(merged, iter.Attribute()) + } + return attribute.NewSet(merged...) +} + +// WithInstrumentationAttributes returns a [LoggerOption] that sets the +// instrumentation attributes of a [Logger]. +// +// This is equivalent to calling WithInstrumentationAttributeSet with an +// [attribute.Set] created from a clone of the passed attributes. +// [WithInstrumentationAttributeSet] is recommended for more control. +// +// If multiple [WithInstrumentationAttributes] or [WithInstrumentationAttributeSet] +// options are passed, the attributes will be merged together in the order +// they are passed. Attributes with duplicate keys will use the last value passed. +func WithInstrumentationAttributes(attr ...attribute.KeyValue) LoggerOption { + set := attribute.NewSet(slices.Clone(attr)...) + return WithInstrumentationAttributeSet(set) +} + +// WithInstrumentationAttributeSet returns a [LoggerOption] that adds the +// instrumentation attributes of a [Logger]. +// +// If multiple [WithInstrumentationAttributes] or [WithInstrumentationAttributeSet] +// options are passed, the attributes will be merged together in the order +// they are passed. Attributes with duplicate keys will use the last value passed. +func WithInstrumentationAttributeSet(set attribute.Set) LoggerOption { + if set.Len() == 0 { + return loggerOptionFunc(func(config LoggerConfig) LoggerConfig { + return config + }) + } + + return loggerOptionFunc(func(config LoggerConfig) LoggerConfig { + if config.attrs.Len() == 0 { + config.attrs = set + } else { + config.attrs = mergeSets(config.attrs, set) + } + return config + }) +} + +// WithSchemaURL returns a [LoggerOption] that sets the schema URL for a +// [Logger]. +func WithSchemaURL(schemaURL string) LoggerOption { + return loggerOptionFunc(func(config LoggerConfig) LoggerConfig { + config.schemaURL = schemaURL + return config + }) +} + +// EnabledParameters represents payload for [Logger]'s Enabled method. +type EnabledParameters struct { + Severity Severity + EventName string +} diff --git a/vendor/go.opentelemetry.io/otel/log/noop/README.md b/vendor/go.opentelemetry.io/otel/log/noop/README.md new file mode 100644 index 0000000000..da08ea6381 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/log/noop/README.md @@ -0,0 +1,3 @@ +# Log Noop + +[![PkgGoDev](https://pkg.go.dev/badge/go.opentelemetry.io/otel/log/noop)](https://pkg.go.dev/go.opentelemetry.io/otel/log/noop) diff --git a/vendor/go.opentelemetry.io/otel/log/noop/noop.go b/vendor/go.opentelemetry.io/otel/log/noop/noop.go new file mode 100644 index 0000000000..d779e5d80d --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/log/noop/noop.go @@ -0,0 +1,50 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// Package noop provides an implementation of the [OpenTelemetry Logs Bridge +// API] that produces no telemetry and minimizes used computation resources. +// +// Using this package to implement the [OpenTelemetry Logs API] will +// effectively disable OpenTelemetry. +// +// This implementation can be embedded in other implementations of the +// [OpenTelemetry Logs API]. Doing so will mean the implementation +// defaults to no operation for methods it does not implement. +// +// [OpenTelemetry Logs API]: https://pkg.go.dev/go.opentelemetry.io/otel/log +package noop // import "go.opentelemetry.io/otel/log/noop" + +import ( + "context" + + "go.opentelemetry.io/otel/log" + "go.opentelemetry.io/otel/log/embedded" +) + +var ( + // Compile-time check this implements the OpenTelemetry API. + _ log.LoggerProvider = LoggerProvider{} + _ log.Logger = Logger{} +) + +// LoggerProvider is an OpenTelemetry No-Op LoggerProvider. +type LoggerProvider struct{ embedded.LoggerProvider } + +// NewLoggerProvider returns a LoggerProvider that does not record any telemetry. +func NewLoggerProvider() LoggerProvider { + return LoggerProvider{} +} + +// Logger returns an OpenTelemetry Logger that does not record any telemetry. +func (LoggerProvider) Logger(string, ...log.LoggerOption) log.Logger { + return Logger{} +} + +// Logger is an OpenTelemetry No-Op Logger. +type Logger struct{ embedded.Logger } + +// Emit does nothing. +func (Logger) Emit(context.Context, log.Record) {} + +// Enabled returns false. No log records are ever emitted. +func (Logger) Enabled(context.Context, log.EnabledParameters) bool { return false } diff --git a/vendor/go.opentelemetry.io/otel/log/provider.go b/vendor/go.opentelemetry.io/otel/log/provider.go new file mode 100644 index 0000000000..5c8ca328f8 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/log/provider.go @@ -0,0 +1,37 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package log // import "go.opentelemetry.io/otel/log" + +import "go.opentelemetry.io/otel/log/embedded" + +// LoggerProvider provides access to [Logger]. +// +// Warning: Methods may be added to this interface in minor releases. See +// package documentation on API implementation for information on how to set +// default behavior for unimplemented methods. +type LoggerProvider interface { + // Users of the interface can ignore this. This embedded type is only used + // by implementations of this interface. See the "API Implementations" + // section of the package documentation for more information. + embedded.LoggerProvider + + // Logger returns a new [Logger] with the provided name and configuration. + // + // The name needs to uniquely identify the source of logged code. It is + // recommended that name is the Go package name of the library using a log + // bridge (note: this is not the name of the bridge package). Most + // commonly, this means a bridge will need to accept this value from its + // users. + // + // If name is empty, implementations need to provide a default name. + // + // The version of the packages using a bridge can be critical information + // to include when logging. The bridge should accept this version + // information and use the [WithInstrumentationVersion] option to configure + // the Logger appropriately. + // + // Implementations of this method need to be safe for a user to call + // concurrently. + Logger(name string, options ...LoggerOption) Logger +} diff --git a/vendor/go.opentelemetry.io/otel/log/record.go b/vendor/go.opentelemetry.io/otel/log/record.go new file mode 100644 index 0000000000..cf62deb9ef --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/log/record.go @@ -0,0 +1,163 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package log // import "go.opentelemetry.io/otel/log" + +import ( + "slices" + "time" +) + +// attributesInlineCount is the number of attributes that are efficiently +// stored in an array within a Record. This value is borrowed from slog which +// performed a quantitative survey of log library use and found this value to +// cover 95% of all use-cases (https://go.dev/blog/slog#performance). +const attributesInlineCount = 5 + +// Record represents a log record. +// A log record with non-empty event name is interpreted as an event record. +type Record struct { + // Ensure forward compatibility by explicitly making this not comparable. + noCmp [0]func() //nolint: unused // This is indeed used. + + eventName string + timestamp time.Time + observedTimestamp time.Time + severity Severity + severityText string + body Value + err error + + // The fields below are for optimizing the implementation of Attributes and + // AddAttributes. This design is borrowed from the slog Record type: + // https://cs.opensource.google/go/go/+/refs/tags/go1.22.0:src/log/slog/record.go;l=20 + + // Allocation optimization: an inline array sized to hold + // the majority of log calls (based on examination of open-source + // code). It holds the start of the list of attributes. + front [attributesInlineCount]KeyValue + + // The number of attributes in front. + nFront int + + // The list of attributes except for those in front. + // Invariants: + // - len(back) > 0 if nFront == len(front) + // - Unused array elements are zero-ed. Used to detect mistakes. + back []KeyValue +} + +// EventName returns the event name. +// A log record with non-empty event name is interpreted as an event record. +func (r *Record) EventName() string { + return r.eventName +} + +// SetEventName sets the event name. +// A log record with non-empty event name is interpreted as an event record. +func (r *Record) SetEventName(s string) { + r.eventName = s +} + +// Timestamp returns the time when the log record occurred. +func (r *Record) Timestamp() time.Time { + return r.timestamp +} + +// SetTimestamp sets the time when the log record occurred. +func (r *Record) SetTimestamp(t time.Time) { + r.timestamp = t +} + +// ObservedTimestamp returns the time when the log record was observed. +func (r *Record) ObservedTimestamp() time.Time { + return r.observedTimestamp +} + +// SetObservedTimestamp sets the time when the log record was observed. +func (r *Record) SetObservedTimestamp(t time.Time) { + r.observedTimestamp = t +} + +// Severity returns the [Severity] of the log record. +func (r *Record) Severity() Severity { + return r.severity +} + +// SetSeverity sets the [Severity] level of the log record. +func (r *Record) SetSeverity(level Severity) { + r.severity = level +} + +// SeverityText returns severity (also known as log level) text. This is the +// original string representation of the severity as it is known at the source. +func (r *Record) SeverityText() string { + return r.severityText +} + +// SetSeverityText sets severity (also known as log level) text. This is the +// original string representation of the severity as it is known at the source. +func (r *Record) SetSeverityText(text string) { + r.severityText = text +} + +// Body returns the body of the log record. +func (r *Record) Body() Value { + return r.body +} + +// SetBody sets the body of the log record. +func (r *Record) SetBody(v Value) { + r.body = v +} + +// Err returns the associated error if one has been set. +func (r *Record) Err() error { + return r.err +} + +// SetErr sets the associated error. Passing nil clears the error. +func (r *Record) SetErr(err error) { + r.err = err +} + +// WalkAttributes walks all attributes the log record holds by calling f for +// each on each [KeyValue] in the [Record]. Iteration stops if f returns false. +func (r *Record) WalkAttributes(f func(KeyValue) bool) { + for i := 0; i < r.nFront; i++ { + if !f(r.front[i]) { + return + } + } + for _, a := range r.back { + if !f(a) { + return + } + } +} + +// AddAttributes adds attributes to the log record. +func (r *Record) AddAttributes(attrs ...KeyValue) { + var i int + for i = 0; i < len(attrs) && r.nFront < len(r.front); i++ { + a := attrs[i] + r.front[r.nFront] = a + r.nFront++ + } + + r.back = slices.Grow(r.back, len(attrs[i:])) + r.back = append(r.back, attrs[i:]...) +} + +// AttributesLen returns the number of attributes in the log record. +func (r *Record) AttributesLen() int { + return r.nFront + len(r.back) +} + +// Clone returns a copy of the record with no shared state. +// The original record and the clone can both be modified without interfering with each other. +func (r *Record) Clone() Record { + res := *r + res.back = slices.Clone(r.back) + return res +} diff --git a/vendor/go.opentelemetry.io/otel/log/severity.go b/vendor/go.opentelemetry.io/otel/log/severity.go new file mode 100644 index 0000000000..0240fd5acb --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/log/severity.go @@ -0,0 +1,64 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +//go:generate stringer -type=Severity -linecomment + +package log // import "go.opentelemetry.io/otel/log" + +// Severity represents a log record severity (also known as log level). Smaller +// numerical values correspond to less severe log records (such as debug +// events), larger numerical values correspond to more severe log records (such +// as errors and critical events). +type Severity int + +// Severity values defined by OpenTelemetry. +const ( + // SeverityUndefined represents an unset Severity. + SeverityUndefined Severity = 0 // UNDEFINED + + // A fine-grained debugging log record. Typically disabled in default + // configurations. + SeverityTrace1 Severity = 1 // TRACE + SeverityTrace2 Severity = 2 // TRACE2 + SeverityTrace3 Severity = 3 // TRACE3 + SeverityTrace4 Severity = 4 // TRACE4 + + // A debugging log record. + SeverityDebug1 Severity = 5 // DEBUG + SeverityDebug2 Severity = 6 // DEBUG2 + SeverityDebug3 Severity = 7 // DEBUG3 + SeverityDebug4 Severity = 8 // DEBUG4 + + // An informational log record. Indicates that an event happened. + SeverityInfo1 Severity = 9 // INFO + SeverityInfo2 Severity = 10 // INFO2 + SeverityInfo3 Severity = 11 // INFO3 + SeverityInfo4 Severity = 12 // INFO4 + + // A warning log record. Not an error but is likely more important than an + // informational event. + SeverityWarn1 Severity = 13 // WARN + SeverityWarn2 Severity = 14 // WARN2 + SeverityWarn3 Severity = 15 // WARN3 + SeverityWarn4 Severity = 16 // WARN4 + + // An error log record. Something went wrong. + SeverityError1 Severity = 17 // ERROR + SeverityError2 Severity = 18 // ERROR2 + SeverityError3 Severity = 19 // ERROR3 + SeverityError4 Severity = 20 // ERROR4 + + // A fatal log record such as application or system crash. + SeverityFatal1 Severity = 21 // FATAL + SeverityFatal2 Severity = 22 // FATAL2 + SeverityFatal3 Severity = 23 // FATAL3 + SeverityFatal4 Severity = 24 // FATAL4 + + // Convenience definitions for the base severity of each level. + SeverityTrace = SeverityTrace1 + SeverityDebug = SeverityDebug1 + SeverityInfo = SeverityInfo1 + SeverityWarn = SeverityWarn1 + SeverityError = SeverityError1 + SeverityFatal = SeverityFatal1 +) diff --git a/vendor/go.opentelemetry.io/otel/log/severity_string.go b/vendor/go.opentelemetry.io/otel/log/severity_string.go new file mode 100644 index 0000000000..fb94caeaba --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/log/severity_string.go @@ -0,0 +1,48 @@ +// Code generated by "stringer -type=Severity -linecomment"; DO NOT EDIT. + +package log + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[SeverityUndefined-0] + _ = x[SeverityTrace1-1] + _ = x[SeverityTrace2-2] + _ = x[SeverityTrace3-3] + _ = x[SeverityTrace4-4] + _ = x[SeverityDebug1-5] + _ = x[SeverityDebug2-6] + _ = x[SeverityDebug3-7] + _ = x[SeverityDebug4-8] + _ = x[SeverityInfo1-9] + _ = x[SeverityInfo2-10] + _ = x[SeverityInfo3-11] + _ = x[SeverityInfo4-12] + _ = x[SeverityWarn1-13] + _ = x[SeverityWarn2-14] + _ = x[SeverityWarn3-15] + _ = x[SeverityWarn4-16] + _ = x[SeverityError1-17] + _ = x[SeverityError2-18] + _ = x[SeverityError3-19] + _ = x[SeverityError4-20] + _ = x[SeverityFatal1-21] + _ = x[SeverityFatal2-22] + _ = x[SeverityFatal3-23] + _ = x[SeverityFatal4-24] +} + +const _Severity_name = "UNDEFINEDTRACETRACE2TRACE3TRACE4DEBUGDEBUG2DEBUG3DEBUG4INFOINFO2INFO3INFO4WARNWARN2WARN3WARN4ERRORERROR2ERROR3ERROR4FATALFATAL2FATAL3FATAL4" + +var _Severity_index = [...]uint8{0, 9, 14, 20, 26, 32, 37, 43, 49, 55, 59, 64, 69, 74, 78, 83, 88, 93, 98, 104, 110, 116, 121, 127, 133, 139} + +func (i Severity) String() string { + idx := int(i) - 0 + if i < 0 || idx >= len(_Severity_index)-1 { + return "Severity(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Severity_name[_Severity_index[idx]:_Severity_index[idx+1]] +} diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/DESIGN.md b/vendor/go.opentelemetry.io/otel/sdk/log/DESIGN.md new file mode 100644 index 0000000000..2e0fb15e29 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/sdk/log/DESIGN.md @@ -0,0 +1,176 @@ +# Logs SDK + +## Abstract + +`go.opentelemetry.io/otel/sdk/log` provides Logs SDK compliant with the +[specification](https://opentelemetry.io/docs/specs/otel/logs/sdk/). + +The prototype was created in +[#4955](https://github.com/open-telemetry/opentelemetry-go/pull/4955). + +## Background + +The goal is to design the exported API of the SDK would have low performance +overhead. Most importantly, have a design that reduces the amount of heap +allocations and even make it possible to have a zero-allocation implementation. +Eliminating the amount of heap allocations reduces the GC pressure which can +produce some of the largest improvements in performance.[^1] + +The main and recommended use case is to configure the SDK to use an OTLP +exporter with a batch processor.[^2] Therefore, the implementation aims to be +high-performant in this scenario. Some users that require high throughput may +also want to use e.g. an [user_events](https://docs.kernel.org/trace/user_events.html), +[LLTng](https://lttng.org/docs/v2.13/#doc-tracing-your-own-user-application) +or [ETW](https://learn.microsoft.com/en-us/windows/win32/etw/about-event-tracing) +exporter with a simple processor. Users may also want to use +[OTLP File](https://opentelemetry.io/docs/specs/otel/protocol/file-exporter/) +or [Standard Output](https://opentelemetry.io/docs/specs/otel/logs/sdk_exporters/stdout/) +exporter in order to emit logs to standard output/error or files. + +## Modules structure + +The SDK is published as a single `go.opentelemetry.io/otel/sdk/log` Go module. + +The exporters are going to be published as following Go modules: + +- `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc` +- `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp` +- `go.opentelemetry.io/otel/exporters/stdout/stdoutlog` + +## LoggerProvider + +The [LoggerProvider](https://opentelemetry.io/docs/specs/otel/logs/sdk/#loggerprovider) +is implemented as `LoggerProvider` struct in [provider.go](provider.go). + +## LogRecord limits + +The [LogRecord limits](https://opentelemetry.io/docs/specs/otel/logs/sdk/#logrecord-limits) +can be configured using following options: + +```go +func WithAttributeCountLimit(limit int) LoggerProviderOption +func WithAttributeValueLengthLimit(limit int) LoggerProviderOption +``` + +The limits can be also configured using the `OTEL_LOGRECORD_*` environment variables as +[defined by the specification](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#logrecord-limits). + +### Processor + +The [LogRecordProcessor](https://opentelemetry.io/docs/specs/otel/logs/sdk/#logrecordprocessor) +is defined as `Processor` interface in [processor.go](processor.go). + +The user set processors for the `LoggerProvider` using +`func WithProcessor(processor Processor) LoggerProviderOption`. + +The user can configure custom processors and decorate built-in processors. + +The specification may add new operations to the +[LogRecordProcessor](https://opentelemetry.io/docs/specs/otel/logs/sdk/#logrecordprocessor). +If it happens, [CONTRIBUTING.md](../../CONTRIBUTING.md#how-to-change-other-interfaces) +describes how the SDK can be extended in a backwards-compatible way. + +### SimpleProcessor + +The [Simple processor](https://opentelemetry.io/docs/specs/otel/logs/sdk/#simple-processor) +is implemented as `SimpleProcessor` struct in [simple.go](simple.go). + +### BatchProcessor + +The [Batching processor](https://opentelemetry.io/docs/specs/otel/logs/sdk/#batching-processor) +is implemented as `BatchProcessor` struct in [batch.go](batch.go). + +The `Batcher` can be also configured using the `OTEL_BLRP_*` environment variables as +[defined by the specification](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#batch-logrecord-processor). + +### Exporter + +The [LogRecordExporter](https://opentelemetry.io/docs/specs/otel/logs/sdk/#logrecordexporter) +is defined as `Exporter` interface in [exporter.go](exporter.go). + +The slice passed to `Export` must not be retained by the implementation +(like e.g. [`io.Writer`](https://pkg.go.dev/io#Writer)) +so that the caller can reuse the passed slice +(e.g. using [`sync.Pool`](https://pkg.go.dev/sync#Pool)) +to avoid heap allocations on each call. + +The specification may add new operations to the +[LogRecordExporter](https://opentelemetry.io/docs/specs/otel/logs/sdk/#logrecordexporter). +If it happens, [CONTRIBUTING.md](../../CONTRIBUTING.md#how-to-change-other-interfaces) +describes how the SDK can be extended in a backwards-compatible way. + +### Record + +The [ReadWriteLogRecord](https://opentelemetry.io/docs/specs/otel/logs/sdk/#readwritelogrecord) +is defined as `Record` struct in [record.go](record.go). + +The `Record` is designed similarly to [`log.Record`](https://pkg.go.dev/go.opentelemetry.io/otel/log#Record) +in order to reduce the number of heap allocations when processing attributes. + +The SDK does not have have an additional definition of +[ReadableLogRecord](https://opentelemetry.io/docs/specs/otel/logs/sdk/#readablelogrecord) +as the specification does not say that the exporters must not be able to modify +the log records. It simply requires them to be able to read the log records. +Having less abstractions reduces the API surface and makes the design simpler. + +## Benchmarking + +The benchmarks are supposed to test end-to-end scenarios +and avoid I/O that could affect the stability of the results. + +The benchmark results can be found in [the prototype](https://github.com/open-telemetry/opentelemetry-go/pull/4955). + +## Rejected alternatives + +### Represent both LogRecordProcessor and LogRecordExporter as Exporter + +Because the [LogRecordProcessor](https://opentelemetry.io/docs/specs/otel/logs/sdk/#logrecordprocessor) +and the [LogRecordProcessor](https://opentelemetry.io/docs/specs/otel/logs/sdk/#logrecordexporter) +abstractions are so similar, there was a proposal to unify them under +single `Exporter` interface.[^3] + +However, introducing a `Processor` interface makes it easier +to create custom processor decorators[^4] +and makes the design more aligned with the specification. + +### Embed log.Record + +Because [`Record`](#record) and [`log.Record`](https://pkg.go.dev/go.opentelemetry.io/otel/log#Record) +are very similar, there was a proposal to embed `log.Record` in `Record` definition. + +[`log.Record`](https://pkg.go.dev/go.opentelemetry.io/otel/log#Record) +supports only adding attributes. +In the SDK, we also need to be able to modify the attributes (e.g. removal) +provided via API. + +Moreover it is safer to have these abstraction decoupled. +E.g. there can be a need for some fields that can be set via API and cannot be modified by the processors. + +### Processor.OnEmit to accept Record values + +There was a proposal to make the [Processor](#processor)'s `OnEmit` +to accept a [Record](#record) value instead of a pointer to reduce allocations +as well as to have design similar to [`slog.Handler`](https://pkg.go.dev/log/slog#Handler). + +There have been long discussions within the OpenTelemetry Specification SIG[^5] +about whether such a design would comply with the specification. The summary +was that the current processor design flaws are present in other languages as +well. Therefore, it would be favorable to introduce new processing concepts +(e.g. chaining processors) in the specification that would coexist with the +current "mutable" processor design. + +The performance disadvantages caused by using a pointer (which at the time of +writing causes an additional heap allocation) may be mitigated by future +versions of the Go compiler, thanks to improved escape analysis and +profile-guided optimization (PGO)[^6]. + +On the other hand, [Processor](#processor)'s `Enabled` is fine to accept +a [Record](#record) value as the processors should not mutate the passed +parameters. + +[^1]: [A Guide to the Go Garbage Collector](https://tip.golang.org/doc/gc-guide) +[^2]: [OpenTelemetry Logging](https://opentelemetry.io/docs/specs/otel/logs) +[^3]: [Conversation on representing LogRecordProcessor and LogRecordExporter via a single Exporter interface](https://github.com/open-telemetry/opentelemetry-go/pull/4954#discussion_r1515050480) +[^4]: [Introduce Processor](https://github.com/pellared/opentelemetry-go/pull/9) +[^5]: [Log record mutations do not have to be visible in next registered processors](https://github.com/open-telemetry/opentelemetry-specification/pull/4067) +[^6]: [Profile-guided optimization](https://go.dev/doc/pgo) diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/LICENSE b/vendor/go.opentelemetry.io/otel/sdk/log/LICENSE new file mode 100644 index 0000000000..f1aee0f110 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/sdk/log/LICENSE @@ -0,0 +1,231 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +-------------------------------------------------------------------------------- + +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/README.md b/vendor/go.opentelemetry.io/otel/sdk/log/README.md new file mode 100644 index 0000000000..729aca0913 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/sdk/log/README.md @@ -0,0 +1,3 @@ +# Log SDK + +[![PkgGoDev](https://pkg.go.dev/badge/go.opentelemetry.io/otel/sdk/log)](https://pkg.go.dev/go.opentelemetry.io/otel/sdk/log) diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/batch.go b/vendor/go.opentelemetry.io/otel/sdk/log/batch.go new file mode 100644 index 0000000000..a03248eba2 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/sdk/log/batch.go @@ -0,0 +1,496 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package log // import "go.opentelemetry.io/otel/sdk/log" + +import ( + "context" + "errors" + "slices" + "sync" + "sync/atomic" + "time" + + "go.opentelemetry.io/otel/internal/global" +) + +const ( + dfltMaxQSize = 2048 + dfltExpInterval = time.Second + dfltExpTimeout = 30 * time.Second + dfltExpMaxBatchSize = 512 + dfltExpBufferSize = 1 + + envarMaxQSize = "OTEL_BLRP_MAX_QUEUE_SIZE" + envarExpInterval = "OTEL_BLRP_SCHEDULE_DELAY" + envarExpTimeout = "OTEL_BLRP_EXPORT_TIMEOUT" + envarExpMaxBatchSize = "OTEL_BLRP_MAX_EXPORT_BATCH_SIZE" +) + +// Compile-time check BatchProcessor implements Processor. +var _ Processor = (*BatchProcessor)(nil) + +// BatchProcessor is a processor that exports batches of log records. +// +// Use [NewBatchProcessor] to create a BatchProcessor. An empty BatchProcessor +// is shut down by default, no records will be batched or exported. +type BatchProcessor struct { + // The BatchProcessor is designed to provide the highest throughput of + // log records possible while being compatible with OpenTelemetry. The + // entry point of log records is the OnEmit method. This method is designed + // to receive records as fast as possible while still honoring shutdown + // commands. All records received are enqueued to queue. + // + // In order to block OnEmit as little as possible, a separate "poll" + // goroutine is spawned at the creation of a BatchProcessor. This + // goroutine is responsible for batching the queue at regular polled + // intervals, or when it is directly signaled to. + // + // To keep the polling goroutine from backing up, all batches it makes are + // exported with a bufferedExporter. This exporter allows the poll + // goroutine to enqueue an export payload that will be handled in a + // separate goroutine dedicated to the export. This asynchronous behavior + // allows the poll goroutine to maintain accurate interval polling. + // + // ___BatchProcessor____ __Poll Goroutine__ __Export Goroutine__ + // || || || || || || + // || ********** || || || || ********** || + // || Records=>* OnEmit * || || | - ticker || || * export * || + // || ********** || || | - trigger || || ********** || + // || || || || | || || || || + // || || || || | || || || || + // || __________\/___ || || |*********** || || ______/\_______ || + // || (____queue______)>=||=||===|* batch *===||=||=>[_export_buffer_] || + // || || || |*********** || || || + // ||_____________________|| ||__________________|| ||____________________|| + // + // + // The "release valve" in this processing is the record queue. This queue + // is a ring buffer. It will overwrite the oldest records first when writes + // to OnEmit are made faster than the queue can be flushed. If batches + // cannot be flushed to the export buffer, the records will remain in the + // queue. + + // exporter is the bufferedExporter all batches are exported with. + exporter *bufferExporter + + // q is the active queue of records that have not yet been exported. + q *queue + // batchSize is the minimum number of records needed before an export is + // triggered (unless the interval expires). + batchSize int + + // pollTrigger triggers the poll goroutine to flush a batch from the queue. + // This is sent to when it is known that the queue contains at least one + // complete batch. + // + // When a send is made to the channel, the poll loop will be reset after + // the flush. If there is still enough records in the queue for another + // batch the reset of the poll loop will automatically re-trigger itself. + // There is no need for the original sender to monitor and resend. + pollTrigger chan struct{} + // pollKill kills the poll goroutine. This is only expected to be closed + // once by the Shutdown method. + pollKill chan struct{} + // pollDone signals the poll goroutine has completed. + pollDone chan struct{} + + // stopped holds the stopped state of the BatchProcessor. + stopped atomic.Bool + + noCmp [0]func() //nolint: unused // This is indeed used. +} + +// NewBatchProcessor decorates the provided exporter +// so that the log records are batched before exporting. +// +// All of the exporter's methods are called synchronously. +func NewBatchProcessor(exporter Exporter, opts ...BatchProcessorOption) *BatchProcessor { + cfg := newBatchConfig(opts) + if exporter == nil { + // Do not panic on nil export. + exporter = defaultNoopExporter + } + // Order is important here. Wrap the timeoutExporter with the chunkExporter + // to ensure each export completes in timeout (instead of all chunked + // exports). + exporter = newTimeoutExporter(exporter, cfg.expTimeout.Value) + // Use a chunkExporter to ensure ForceFlush and Shutdown calls are batched + // appropriately on export. + exporter = newChunkExporter(exporter, cfg.expMaxBatchSize.Value) + + b := &BatchProcessor{ + exporter: newBufferExporter(exporter, cfg.expBufferSize.Value), + + q: newQueue(cfg.maxQSize.Value), + batchSize: cfg.expMaxBatchSize.Value, + pollTrigger: make(chan struct{}, 1), + pollKill: make(chan struct{}), + } + b.pollDone = b.poll(cfg.expInterval.Value) + return b +} + +// poll spawns a goroutine to handle interval polling and batch exporting. The +// returned done chan is closed when the spawned goroutine completes. +func (b *BatchProcessor) poll(interval time.Duration) (done chan struct{}) { + done = make(chan struct{}) + + ticker := time.NewTicker(interval) + // TODO: investigate using a sync.Pool instead of cloning. + buf := make([]Record, b.batchSize) + go func() { + defer close(done) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + case <-b.pollTrigger: + ticker.Reset(interval) + case <-b.pollKill: + return + } + + if d := b.q.Dropped(); d > 0 { + global.Warn("dropped log records", "dropped", d) + } + + var qLen int + // Don't copy data from queue unless exporter can accept more, it is very expensive. + if b.exporter.Ready() { + qLen = b.q.TryDequeue(buf, func(r []Record) bool { + ok := b.exporter.EnqueueExport(r) + if ok { + buf = slices.Clone(buf) + } + return ok + }) + } else { + qLen = b.q.Len() + } + + if qLen >= b.batchSize { + // There is another full batch ready. Immediately trigger + // another export attempt. + select { + case b.pollTrigger <- struct{}{}: + default: + // Another flush signal already received. + } + } + } + }() + return done +} + +// Enabled returns true, indicating this Processor will process all records. +func (*BatchProcessor) Enabled(context.Context, EnabledParameters) bool { + return true +} + +// OnEmit batches provided log record. +func (b *BatchProcessor) OnEmit(_ context.Context, r *Record) error { + if b.stopped.Load() || b.q == nil { + return nil + } + // The record is cloned so that changes done by subsequent processors + // are not going to lead to a data race. + if n := b.q.Enqueue(r.Clone()); n >= b.batchSize { + select { + case b.pollTrigger <- struct{}{}: + default: + // Flush chan full. The poll goroutine will handle this by + // re-sending any trigger until the queue has less than batchSize + // records. + } + } + return nil +} + +// Shutdown flushes queued log records and shuts down the decorated exporter. +func (b *BatchProcessor) Shutdown(ctx context.Context) error { + if b.stopped.Swap(true) || b.q == nil { + return nil + } + + // Stop the poll goroutine. + close(b.pollKill) + select { + case <-b.pollDone: + case <-ctx.Done(): + // Out of time. + return errors.Join(ctx.Err(), b.exporter.Shutdown(ctx)) + } + + // Flush remaining queued before exporter shutdown. + err := b.exporter.Export(ctx, b.q.Flush()) + return errors.Join(err, b.exporter.Shutdown(ctx)) +} + +var errPartialFlush = errors.New("partial flush: export buffer full") + +// Used for testing. +var ctxErr = func(ctx context.Context) error { + return ctx.Err() +} + +// ForceFlush flushes queued log records and flushes the decorated exporter. +func (b *BatchProcessor) ForceFlush(ctx context.Context) error { + if b.stopped.Load() || b.q == nil { + return nil + } + + buf := make([]Record, b.q.cap) + notFlushed := func() bool { + var flushed bool + _ = b.q.TryDequeue(buf, func(r []Record) bool { + flushed = b.exporter.EnqueueExport(r) + return flushed + }) + return !flushed + } + var err error + // For as long as ctx allows, try to make a single flush of the queue. + for notFlushed() { + // Use ctxErr instead of calling ctx.Err directly so we can test + // the partial error return. + if e := ctxErr(ctx); e != nil { + err = errors.Join(e, errPartialFlush) + break + } + } + return errors.Join(err, b.exporter.ForceFlush(ctx)) +} + +// queue holds a queue of logging records. +// +// When the queue becomes full, the oldest records in the queue are +// overwritten. +type queue struct { + sync.Mutex + + dropped atomic.Uint64 + cap, len int + read, write *ring +} + +func newQueue(size int) *queue { + r := newRing(size) + return &queue{ + cap: size, + read: r, + write: r, + } +} + +func (q *queue) Len() int { + q.Lock() + defer q.Unlock() + + return q.len +} + +// Dropped returns the number of Records dropped during enqueueing since the +// last time Dropped was called. +func (q *queue) Dropped() uint64 { + return q.dropped.Swap(0) +} + +// Enqueue adds r to the queue. The queue size, including the addition of r, is +// returned. +// +// If enqueueing r will exceed the capacity of q, the oldest Record held in q +// will be dropped and r retained. +func (q *queue) Enqueue(r Record) int { + q.Lock() + defer q.Unlock() + + q.write.Value = r + q.write = q.write.Next() + + q.len++ + if q.len > q.cap { + // Overflow. Advance read to be the new "oldest". + q.len = q.cap + q.read = q.read.Next() + q.dropped.Add(1) + } + return q.len +} + +// TryDequeue attempts to dequeue up to len(buf) Records. The available Records +// will be assigned into buf and passed to write. If write fails, returning +// false, the Records will not be removed from the queue. If write succeeds, +// returning true, the dequeued Records are removed from the queue. The number +// of Records remaining in the queue are returned. +// +// When write is called the lock of q is held. The write function must not call +// other methods of this q that acquire the lock. +func (q *queue) TryDequeue(buf []Record, write func([]Record) bool) int { + q.Lock() + defer q.Unlock() + + origRead := q.read + + n := min(len(buf), q.len) + for i := range n { + buf[i] = q.read.Value // nolint:gosec // n is bounded by len(buf) + q.read = q.read.Next() + } + + if write(buf[:n]) { + q.len -= n + } else { + q.read = origRead + } + return q.len +} + +// Flush returns all the Records held in the queue and resets it to be +// empty. +func (q *queue) Flush() []Record { + q.Lock() + defer q.Unlock() + + out := make([]Record, q.len) + for i := range out { + out[i] = q.read.Value + q.read = q.read.Next() + } + q.len = 0 + + return out +} + +type batchConfig struct { + maxQSize setting[int] + expInterval setting[time.Duration] + expTimeout setting[time.Duration] + expMaxBatchSize setting[int] + expBufferSize setting[int] +} + +func newBatchConfig(options []BatchProcessorOption) batchConfig { + var c batchConfig + for _, o := range options { + c = o.apply(c) + } + + c.maxQSize = c.maxQSize.Resolve( + clearLessThanOne[int](), + getenv[int](envarMaxQSize), + clearLessThanOne[int](), // nolint:gocritic // the function argument is duplicated on purpose + fallback[int](dfltMaxQSize), + ) + c.expInterval = c.expInterval.Resolve( + clearLessThanOne[time.Duration](), + getenv[time.Duration](envarExpInterval), + clearLessThanOne[time.Duration](), // nolint:gocritic // the function argument is duplicated on purpose + fallback[time.Duration](dfltExpInterval), + ) + c.expTimeout = c.expTimeout.Resolve( + clearLessThanOne[time.Duration](), + getenv[time.Duration](envarExpTimeout), + clearLessThanOne[time.Duration](), // nolint:gocritic // the function argument is duplicated on purpose + fallback[time.Duration](dfltExpTimeout), + ) + c.expMaxBatchSize = c.expMaxBatchSize.Resolve( + clearLessThanOne[int](), + getenv[int](envarExpMaxBatchSize), + clearLessThanOne[int](), // nolint:gocritic // the function argument is duplicated on purpose + clampMax[int](c.maxQSize.Value), + fallback[int](dfltExpMaxBatchSize), + ) + c.expBufferSize = c.expBufferSize.Resolve( + clearLessThanOne[int](), + fallback[int](dfltExpBufferSize), + ) + + return c +} + +// BatchProcessorOption applies a configuration to a [BatchProcessor]. +type BatchProcessorOption interface { + apply(batchConfig) batchConfig +} + +type batchOptionFunc func(batchConfig) batchConfig + +func (fn batchOptionFunc) apply(c batchConfig) batchConfig { + return fn(c) +} + +// WithMaxQueueSize sets the maximum queue size used by the Batcher. +// After the size is reached log records are dropped. +// +// If the OTEL_BLRP_MAX_QUEUE_SIZE environment variable is set, +// and this option is not passed, that variable value will be used. +// +// By default, if an environment variable is not set, and this option is not +// passed, 2048 will be used. +// The default value is also used when the provided value is less than one. +func WithMaxQueueSize(size int) BatchProcessorOption { + return batchOptionFunc(func(cfg batchConfig) batchConfig { + cfg.maxQSize = newSetting(size) + return cfg + }) +} + +// WithExportInterval sets the maximum duration between batched exports. +// +// If the OTEL_BLRP_SCHEDULE_DELAY environment variable is set, +// and this option is not passed, that variable value will be used. +// +// By default, if an environment variable is not set, and this option is not +// passed, 1s will be used. +// The default value is also used when the provided value is less than one. +func WithExportInterval(d time.Duration) BatchProcessorOption { + return batchOptionFunc(func(cfg batchConfig) batchConfig { + cfg.expInterval = newSetting(d) + return cfg + }) +} + +// WithExportTimeout sets the duration after which a batched export is canceled. +// +// If the OTEL_BLRP_EXPORT_TIMEOUT environment variable is set, +// and this option is not passed, that variable value will be used. +// +// By default, if an environment variable is not set, and this option is not +// passed, 30s will be used. +// The default value is also used when the provided value is less than one. +func WithExportTimeout(d time.Duration) BatchProcessorOption { + return batchOptionFunc(func(cfg batchConfig) batchConfig { + cfg.expTimeout = newSetting(d) + return cfg + }) +} + +// WithExportMaxBatchSize sets the maximum batch size of every export. +// A batch will be split into multiple exports to not exceed this size. +// +// If the OTEL_BLRP_MAX_EXPORT_BATCH_SIZE environment variable is set, +// and this option is not passed, that variable value will be used. +// +// By default, if an environment variable is not set, and this option is not +// passed, 512 will be used. +// The default value is also used when the provided value is less than one. +func WithExportMaxBatchSize(size int) BatchProcessorOption { + return batchOptionFunc(func(cfg batchConfig) batchConfig { + cfg.expMaxBatchSize = newSetting(size) + return cfg + }) +} + +// WithExportBufferSize sets the batch buffer size. +// Batches will be temporarily kept in a memory buffer until they are exported. +// +// By default, a value of 1 will be used. +// The default value is also used when the provided value is less than one. +func WithExportBufferSize(size int) BatchProcessorOption { + return batchOptionFunc(func(cfg batchConfig) batchConfig { + cfg.expBufferSize = newSetting(size) + return cfg + }) +} diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/doc.go b/vendor/go.opentelemetry.io/otel/sdk/log/doc.go new file mode 100644 index 0000000000..a27834a5b3 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/sdk/log/doc.go @@ -0,0 +1,39 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +/* +Package log provides the OpenTelemetry Logs SDK. + +See https://opentelemetry.io/docs/concepts/signals/logs/ for information +about the concept of OpenTelemetry Logs and +https://opentelemetry.io/docs/concepts/components/ for more information +about OpenTelemetry SDKs. + +The entry point for the log package is [NewLoggerProvider]. +[LoggerProvider] is the object that all Bridge API calls use to create +Loggers, and ultimately emit log records. +Also, it is an object that should be used to +control the life-cycle (start, flush, and shutdown) of the Logs SDK. + +A LoggerProvider needs to be configured to process the log records, this is +done by configuring it with a [Processor] implementation using [WithProcessor]. +The log package provides the [BatchProcessor] and [SimpleProcessor] +that are configured with an [Exporter] implementation which +exports the log records to given destination. See +[go.opentelemetry.io/otel/exporters] for exporters that can be used with these +Processors. + +The data generated by a LoggerProvider needs to include information about its +origin. A LoggerProvider needs to be configured with a Resource, by using +[WithResource], to include this information. This Resource +should be used to describe the unique runtime environment instrumented code +is being run on. That way when multiple instances of the code are collected +at a single endpoint their origin is decipherable. + +See [go.opentelemetry.io/otel/sdk/log/internal/x] for information about +the experimental features. + +See [go.opentelemetry.io/otel/log] for more information about +the OpenTelemetry Logs API. +*/ +package log // import "go.opentelemetry.io/otel/sdk/log" diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/exporter.go b/vendor/go.opentelemetry.io/otel/sdk/log/exporter.go new file mode 100644 index 0000000000..a9d3c439ba --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/sdk/log/exporter.go @@ -0,0 +1,326 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package log // import "go.opentelemetry.io/otel/sdk/log" + +import ( + "context" + "errors" + "fmt" + "sync" + "sync/atomic" + "time" + + "go.opentelemetry.io/otel" +) + +// Exporter handles the delivery of log records to external receivers. +type Exporter interface { + // Export transmits log records to a receiver. + // + // The deadline or cancellation of the passed context must be honored. An + // appropriate error should be returned in these situations. + // + // All retry logic must be contained in this function. The SDK does not + // implement any retry logic. All errors returned by this function are + // considered unrecoverable and will be reported to a configured error + // Handler. + // + // Implementations must not retain the records slice. + // + // Before modifying a Record, the implementation must use Record.Clone + // to create a copy that shares no state with the original. + // + // Export should never be called concurrently with other Export calls. + // However, it may be called concurrently with other methods. + Export(ctx context.Context, records []Record) error + + // Shutdown is called when the SDK shuts down. Any cleanup or release of + // resources held by the exporter should be done in this call. + // + // The deadline or cancellation of the passed context must be honored. An + // appropriate error should be returned in these situations. + // + // After Shutdown is called, calls to Export, Shutdown, or ForceFlush + // should perform no operation and return nil error. + // + // Shutdown may be called concurrently with itself or with other methods. + Shutdown(ctx context.Context) error + + // ForceFlush exports log records to the configured Exporter that have not yet + // been exported. + // + // The deadline or cancellation of the passed context must be honored. An + // appropriate error should be returned in these situations. + // + // ForceFlush may be called concurrently with itself or with other methods. + ForceFlush(ctx context.Context) error +} + +var defaultNoopExporter = &noopExporter{} + +type noopExporter struct{} + +func (noopExporter) Export(context.Context, []Record) error { return nil } + +func (noopExporter) Shutdown(context.Context) error { return nil } + +func (noopExporter) ForceFlush(context.Context) error { return nil } + +// chunkExporter wraps an Exporter's Export method so it is called with +// appropriately sized export payloads. Any payload larger than a defined size +// is chunked into smaller payloads and exported sequentially. +type chunkExporter struct { + Exporter + + // size is the maximum batch size exported. + size int +} + +// newChunkExporter wraps exporter. Calls to the Export will have their records +// payload chunked so they do not exceed size. If size is less than or equal +// to 0, exporter is returned directly. +func newChunkExporter(exporter Exporter, size int) Exporter { + if size <= 0 { + return exporter + } + return &chunkExporter{Exporter: exporter, size: size} +} + +// Export exports records in chunks no larger than c.size. +func (c chunkExporter) Export(ctx context.Context, records []Record) error { + n := len(records) + for i, j := 0, min(c.size, n); i < n; i, j = i+c.size, min(j+c.size, n) { + if err := c.Exporter.Export(ctx, records[i:j]); err != nil { + return err + } + } + return nil +} + +// timeoutExporter wraps an Exporter and ensures any call to Export will have a +// timeout for the context. +type timeoutExporter struct { + Exporter + + // timeout is the maximum time an export is attempted. + timeout time.Duration +} + +// newTimeoutExporter wraps exporter with an Exporter that limits the context +// lifetime passed to Export to be timeout. If timeout is less than or equal to +// zero, exporter will be returned directly. +func newTimeoutExporter(exp Exporter, timeout time.Duration) Exporter { + if timeout <= 0 { + return exp + } + return &timeoutExporter{Exporter: exp, timeout: timeout} +} + +// Export sets the timeout of ctx before calling the Exporter e wraps. +func (e *timeoutExporter) Export(ctx context.Context, records []Record) error { + // This only used by the batch processor, and it takes processor timeout config. + // Thus, the error message points to the processor. So users know they should adjust the processor timeout. + ctx, cancel := context.WithTimeoutCause(ctx, e.timeout, errors.New("processor export timeout")) + defer cancel() + return e.Exporter.Export(ctx, records) +} + +// exportSync exports all data from input using exporter in a spawned +// goroutine. The returned chan will be closed when the spawned goroutine +// completes. +func exportSync(input <-chan exportData, exporter Exporter) (done chan struct{}) { + done = make(chan struct{}) + go func() { + defer close(done) + for data := range input { + data.DoExport(exporter.Export) + } + }() + return done +} + +// exportData is data related to an export. +type exportData struct { + ctx context.Context + records []Record + + // respCh is the channel any error returned from the export will be sent + // on. If this is nil, and the export error is non-nil, the error will + // passed to the OTel error handler. + respCh chan<- error +} + +// DoExport calls exportFn with the data contained in e. The error response +// will be returned on e's respCh if not nil. The error will be handled by the +// default OTel error handle if it is not nil and respCh is nil or full. +func (e exportData) DoExport(exportFn func(context.Context, []Record) error) { + if len(e.records) == 0 { + e.respond(nil) + return + } + + e.respond(exportFn(e.ctx, e.records)) +} + +func (e exportData) respond(err error) { + select { + case e.respCh <- err: + default: + // e.respCh is nil or busy, default to otel.Handler. + if err != nil { + otel.Handle(err) + } + } +} + +// bufferExporter provides asynchronous and synchronous export functionality by +// buffering export requests. +type bufferExporter struct { + Exporter + + input chan exportData + inputMu sync.Mutex + + done chan struct{} + stopped atomic.Bool +} + +// newBufferExporter returns a new bufferExporter that wraps exporter. The +// returned bufferExporter will buffer at most size number of export requests. +// If size is less than 1, 1 will be used. +func newBufferExporter(exporter Exporter, size int) *bufferExporter { + if size < 1 { + size = 1 + } + input := make(chan exportData, size) + return &bufferExporter{ + Exporter: exporter, + + input: input, + done: exportSync(input, exporter), + } +} + +func (e *bufferExporter) Ready() bool { + return len(e.input) != cap(e.input) +} + +var errStopped = errors.New("exporter stopped") + +func (e *bufferExporter) enqueue(ctx context.Context, records []Record, rCh chan<- error) error { + data := exportData{ctx, records, rCh} + + e.inputMu.Lock() + defer e.inputMu.Unlock() + + // Check stopped before enqueueing now that e.inputMu is held. This + // prevents sends on a closed chan when Shutdown is called concurrently. + if e.stopped.Load() { + return errStopped + } + + select { + case e.input <- data: + case <-ctx.Done(): + return ctx.Err() + } + return nil +} + +// EnqueueExport enqueues an export of records in the context of ctx to be +// performed asynchronously. This will return true if the records are +// successfully enqueued (or the bufferExporter is shut down), false otherwise. +// +// The passed records are held after this call returns. +func (e *bufferExporter) EnqueueExport(records []Record) bool { + if len(records) == 0 { + // Nothing to enqueue, do not waste input space. + return true + } + + data := exportData{ctx: context.Background(), records: records} + + e.inputMu.Lock() + defer e.inputMu.Unlock() + + // Check stopped before enqueueing now that e.inputMu is held. This + // prevents sends on a closed chan when Shutdown is called concurrently. + if e.stopped.Load() { + return true + } + + select { + case e.input <- data: + return true + default: + return false + } +} + +// Export synchronously exports records in the context of ctx. This will not +// return until the export has been completed. +func (e *bufferExporter) Export(ctx context.Context, records []Record) error { + if len(records) == 0 { + return nil + } + + resp := make(chan error, 1) + err := e.enqueue(ctx, records, resp) + if err != nil { + if errors.Is(err, errStopped) { + return nil + } + return fmt.Errorf("%w: dropping %d records", err, len(records)) + } + + select { + case err := <-resp: + return err + case <-ctx.Done(): + return ctx.Err() + } +} + +// ForceFlush flushes buffered exports. Any existing exports that is buffered +// is flushed before this returns. +func (e *bufferExporter) ForceFlush(ctx context.Context) error { + resp := make(chan error, 1) + err := e.enqueue(ctx, nil, resp) + if err != nil { + if errors.Is(err, errStopped) { + return nil + } + return err + } + + select { + case <-resp: + case <-ctx.Done(): + return ctx.Err() + } + return e.Exporter.ForceFlush(ctx) +} + +// Shutdown shuts down e. +// +// Any buffered exports are flushed before this returns. +// +// All calls to EnqueueExport or Exporter will return nil without any export +// after this is called. +func (e *bufferExporter) Shutdown(ctx context.Context) error { + if e.stopped.Swap(true) { + return nil + } + e.inputMu.Lock() + defer e.inputMu.Unlock() + + // No more sends will be made. + close(e.input) + select { + case <-e.done: + case <-ctx.Done(): + return errors.Join(ctx.Err(), e.Exporter.Shutdown(ctx)) + } + return e.Exporter.Shutdown(ctx) +} diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/instrumentation.go b/vendor/go.opentelemetry.io/otel/sdk/log/instrumentation.go new file mode 100644 index 0000000000..5e496f7e24 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/sdk/log/instrumentation.go @@ -0,0 +1,39 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package log // import "go.opentelemetry.io/otel/sdk/log" + +import ( + "context" + "fmt" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/sdk" + "go.opentelemetry.io/otel/sdk/log/internal/x" + semconv "go.opentelemetry.io/otel/semconv/v1.41.0" + "go.opentelemetry.io/otel/semconv/v1.41.0/otelconv" +) + +// newRecordCounterIncr returns a function that increments the log record +// counter metric. If observability is disabled, it returns nil. +func newRecordCounterIncr() (func(context.Context), error) { + if !x.Observability.Enabled() { + return nil, nil + } + + m := otel.GetMeterProvider().Meter( + "go.opentelemetry.io/otel/sdk/log", + metric.WithInstrumentationVersion(sdk.Version()), + metric.WithSchemaURL(semconv.SchemaURL), + ) + + created, err := otelconv.NewSDKLogCreated(m) + if err != nil { + err = fmt.Errorf("failed to create log created metric: %w", err) + return nil, err + } + inst := created.Inst() + f := func(ctx context.Context) { inst.Add(ctx, 1) } + return f, nil +} diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/internal/observ/doc.go b/vendor/go.opentelemetry.io/otel/sdk/log/internal/observ/doc.go new file mode 100644 index 0000000000..6879567cbf --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/sdk/log/internal/observ/doc.go @@ -0,0 +1,6 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// Package observ provides observability instrumentation for the OTel log SDK +// package. +package observ // import "go.opentelemetry.io/otel/sdk/log/internal/observ" diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/internal/observ/simple_log_processor.go b/vendor/go.opentelemetry.io/otel/sdk/log/internal/observ/simple_log_processor.go new file mode 100644 index 0000000000..916bdc67e0 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/sdk/log/internal/observ/simple_log_processor.go @@ -0,0 +1,129 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package observ // import "go.opentelemetry.io/otel/sdk/log/internal/observ" + +import ( + "context" + "fmt" + "sync" + "sync/atomic" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/sdk" + "go.opentelemetry.io/otel/sdk/log/internal/x" + semconv "go.opentelemetry.io/otel/semconv/v1.41.0" + "go.opentelemetry.io/otel/semconv/v1.41.0/otelconv" +) + +const ( + // ScopeName is the name of the instrumentation scope. + ScopeName = "go.opentelemetry.io/otel/sdk/log/internal/observ" +) + +var measureAttrsPool = sync.Pool{ + New: func() any { + // "component.name" + "component.type" + "error.type" + const n = 1 + 1 + 1 + s := make([]attribute.KeyValue, 0, n) + // Return a pointer to a slice instead of a slice itself + // to avoid allocations on every call. + return &s + }, +} + +// simpleProcessorN is a global 0-based count of the number of simple processor created. +var simpleProcessorN atomic.Int64 + +// NextSimpleProcessorID returns the next unique ID for a simpleProcessor. +func NextSimpleProcessorID() int64 { + const inc = 1 + return simpleProcessorN.Add(inc) - inc +} + +// SetSimpleProcessorID sets the exporter ID counter to v and returns the previous +// value. +// +// This function is useful for testing purposes, allowing you to reset the +// counter. It should not be used in production code. +func SetSimpleProcessorID(v int64) int64 { + return simpleProcessorN.Swap(v) +} + +// GetSLPComponentName returns the component name attribute for a +// SimpleLogProcessor with the given ID. +func GetSLPComponentName(id int64) attribute.KeyValue { + t := otelconv.ComponentTypeSimpleLogProcessor + name := fmt.Sprintf("%s/%d", t, id) + return semconv.OTelComponentName(name) +} + +// SLP is the instrumentation for an OTel SDK SimpleLogProcessor. +type SLP struct { + processed metric.Int64Counter + attrs []attribute.KeyValue + addOpts []metric.AddOption +} + +// NewSLP returns instrumentation for an OTel SDK SimpleLogProcessor with the +// provided ID. +// +// If the experimental observability is disabled, nil is returned. +func NewSLP(id int64) (*SLP, error) { + if !x.Observability.Enabled() { + return nil, nil + } + + meter := otel.GetMeterProvider() + mt := meter.Meter( + ScopeName, + metric.WithInstrumentationVersion(sdk.Version()), + metric.WithSchemaURL(semconv.SchemaURL), + ) + + p, err := otelconv.NewSDKProcessorLogProcessed(mt) + if err != nil { + err = fmt.Errorf("failed to create a processed log metric: %w", err) + return nil, err + } + + name := GetSLPComponentName(id) + componentType := p.AttrComponentType(otelconv.ComponentTypeSimpleLogProcessor) + attrs := []attribute.KeyValue{name, componentType} + addOpts := []metric.AddOption{metric.WithAttributeSet(attribute.NewSet(attrs...))} + + return &SLP{ + processed: p.Inst(), + attrs: attrs, + addOpts: addOpts, + }, nil +} + +// LogProcessed records that a log has been processed by the SimpleLogProcessor. +// If err is non-nil, it records the processing error as an attribute. +func (slp *SLP) LogProcessed(ctx context.Context, err error) { + if slp.processed.Enabled(ctx) { + slp.processed.Add(ctx, 1, slp.addOption(err)...) + } +} + +func (slp *SLP) addOption(err error) []metric.AddOption { + if err == nil { + return slp.addOpts + } + attrs := measureAttrsPool.Get().(*[]attribute.KeyValue) + defer func() { + clear(*attrs) + *attrs = (*attrs)[:0] // reset the slice + measureAttrsPool.Put(attrs) + }() + + *attrs = append(*attrs, slp.attrs...) + *attrs = append(*attrs, semconv.ErrorType(err)) + + // Do not inefficiently make a copy of attrs by using + // WithAttributes instead of WithAttributeSet. + return []metric.AddOption{metric.WithAttributeSet(attribute.NewSet(*attrs...))} +} diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/internal/x/README.md b/vendor/go.opentelemetry.io/otel/sdk/log/internal/x/README.md new file mode 100644 index 0000000000..33176f78e3 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/sdk/log/internal/x/README.md @@ -0,0 +1,34 @@ +# Experimental Features + +The Logs SDK contains features that have not yet stabilized in the OpenTelemetry specification. +These features are added to the OpenTelemetry Go Logs SDK prior to stabilization in the specification so that users can start experimenting with them and provide feedback. + +These feature may change in backwards incompatible ways as feedback is applied. +See the [Compatibility and Stability](#compatibility-and-stability) section for more information. + +## Features + +- [Observability](#observability) + +### Observability + +The Logs SDK can be configured to provide observability about itself using OpenTelemetry metrics. + +To opt-in, set the environment variable `OTEL_GO_X_OBSERVABILITY` to `true`. + +When enabled, the SDK will create the following metrics using the global `MeterProvider`: + +- `otel.sdk.log.created` + +Please see the [Semantic conventions for OpenTelemetry SDK metrics] documentation for more details on these metrics. + +[Semantic conventions for OpenTelemetry SDK metrics]: https://github.com/open-telemetry/semantic-conventions/blob/v1.36.0/docs/otel/sdk-metrics.md + +## Compatibility and Stability + +Experimental features do not fall within the scope of the OpenTelemetry Go versioning and stability [policy](../../../../VERSIONING.md). +These features may be removed or modified in successive version releases, including patch versions. + +When an experimental feature is promoted to a stable feature, a migration path will be included in the changelog entry of the release. +There is no guarantee that any environment variable feature flags that enabled the experimental feature will be supported by the stable version. +If they are supported, they may be accompanied with a deprecation notice stating a timeline for the removal of that support. diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/internal/x/features.go b/vendor/go.opentelemetry.io/otel/sdk/log/internal/x/features.go new file mode 100644 index 0000000000..e0ac88a425 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/sdk/log/internal/x/features.go @@ -0,0 +1,23 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// Package x documents experimental features for [go.opentelemetry.io/otel/sdk/log]. +package x // import "go.opentelemetry.io/otel/sdk/log/internal/x" + +import "strings" + +// Observability is an experimental feature flag that determines if SDK +// observability metrics are enabled. +// +// To enable this feature set the OTEL_GO_X_OBSERVABILITY environment variable +// to the case-insensitive string value of "true" (i.e. "True" and "TRUE" +// will also enable this). +var Observability = newFeature( + []string{"OBSERVABILITY", "SELF_OBSERVABILITY"}, + func(v string) (string, bool) { + if strings.EqualFold(v, "true") { + return v, true + } + return "", false + }, +) diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/internal/x/x.go b/vendor/go.opentelemetry.io/otel/sdk/log/internal/x/x.go new file mode 100644 index 0000000000..e597efb106 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/sdk/log/internal/x/x.go @@ -0,0 +1,58 @@ +// Code generated by gotmpl. DO NOT MODIFY. +// source: internal/shared/x/x.go.tmpl + +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// Package x documents experimental features for [go.opentelemetry.io/otel/sdk/log]. +package x // import "go.opentelemetry.io/otel/sdk/log/internal/x" + +import ( + "os" +) + +// Feature is an experimental feature control flag. It provides a uniform way +// to interact with these feature flags and parse their values. +type Feature[T any] struct { + keys []string + parse func(v string) (T, bool) +} + +func newFeature[T any](suffix []string, parse func(string) (T, bool)) Feature[T] { + const envKeyRoot = "OTEL_GO_X_" + keys := make([]string, 0, len(suffix)) + for _, s := range suffix { + keys = append(keys, envKeyRoot+s) + } + return Feature[T]{ + keys: keys, + parse: parse, + } +} + +// Keys returns the environment variable keys that can be set to enable the +// feature. +func (f Feature[T]) Keys() []string { return f.keys } + +// Lookup returns the user configured value for the feature and true if the +// user has enabled the feature. Otherwise, if the feature is not enabled, a +// zero-value and false are returned. +func (f Feature[T]) Lookup() (v T, ok bool) { + // https://github.com/open-telemetry/opentelemetry-specification/blob/62effed618589a0bec416a87e559c0a9d96289bb/specification/configuration/sdk-environment-variables.md#parsing-empty-value + // + // > The SDK MUST interpret an empty value of an environment variable the + // > same way as when the variable is unset. + for _, key := range f.keys { + vRaw := os.Getenv(key) + if vRaw != "" { + return f.parse(vRaw) + } + } + return v, ok +} + +// Enabled reports whether the feature is enabled. +func (f Feature[T]) Enabled() bool { + _, ok := f.Lookup() + return ok +} diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/logger.go b/vendor/go.opentelemetry.io/otel/sdk/log/logger.go new file mode 100644 index 0000000000..ce63f4e300 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/sdk/log/logger.go @@ -0,0 +1,200 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package log // import "go.opentelemetry.io/otel/sdk/log" + +import ( + "context" + "errors" + "fmt" + "reflect" + "time" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/log" + "go.opentelemetry.io/otel/log/embedded" + "go.opentelemetry.io/otel/sdk/instrumentation" + semconv "go.opentelemetry.io/otel/semconv/v1.41.0" + "go.opentelemetry.io/otel/trace" +) + +var now = time.Now + +const ( + exceptionTypeKey = string(semconv.ExceptionTypeKey) + exceptionMessageKey = string(semconv.ExceptionMessageKey) + exceptionStacktraceKey = string(semconv.ExceptionStacktraceKey) +) + +// Compile-time check logger implements log.Logger. +var _ log.Logger = (*logger)(nil) + +type logger struct { + embedded.Logger + + provider *LoggerProvider + instrumentationScope instrumentation.Scope + + // recCntIncr increments the count of log records created. It will be nil + // if observability is disabled. + recCntIncr func(context.Context) +} + +func newLogger(p *LoggerProvider, scope instrumentation.Scope) *logger { + l := &logger{ + provider: p, + instrumentationScope: scope, + } + + var err error + l.recCntIncr, err = newRecordCounterIncr() + if err != nil { + otel.Handle(err) + } + return l +} + +func (l *logger) Emit(ctx context.Context, r log.Record) { + newRecord := l.newRecord(ctx, r) + for _, p := range l.provider.processors { + if err := p.OnEmit(ctx, &newRecord); err != nil { + otel.Handle(err) + } + } +} + +// Enabled returns true if at least one Processor held by the LoggerProvider +// that created the logger will process for the provided context and param. +// +// If it is not possible to definitively determine the record will be +// processed, true will be returned by default. A value of false will only be +// returned if it can be positively verified that no Processor will process. +func (l *logger) Enabled(ctx context.Context, param log.EnabledParameters) bool { + p := EnabledParameters{ + InstrumentationScope: l.instrumentationScope, + Severity: param.Severity, + EventName: param.EventName, + } + + for _, processor := range l.provider.processors { + if processor.Enabled(ctx, p) { + // At least one Processor will process the Record. + return true + } + } + // No Processor will process the record. + return false +} + +func (l *logger) newRecord(ctx context.Context, r log.Record) Record { + sc := trace.SpanContextFromContext(ctx) + + newRecord := Record{ + eventName: r.EventName(), + timestamp: r.Timestamp(), + observedTimestamp: r.ObservedTimestamp(), + severity: r.Severity(), + severityText: r.SeverityText(), + + traceID: sc.TraceID(), + spanID: sc.SpanID(), + traceFlags: sc.TraceFlags(), + + resource: l.provider.resource, + scope: &l.instrumentationScope, + attributeValueLengthLimit: l.provider.attributeValueLengthLimit, + attributeCountLimit: l.provider.attributeCountLimit, + allowDupKeys: l.provider.allowDupKeys, + } + if l.recCntIncr != nil { + l.recCntIncr(ctx) + } + + // This ensures we deduplicate key-value collections in the log body + newRecord.SetBody(r.Body()) + + // This field SHOULD be set once the event is observed by OpenTelemetry. + if newRecord.observedTimestamp.IsZero() { + newRecord.observedTimestamp = now() + } + + hasExceptionAttr := false + r.WalkAttributes(func(kv log.KeyValue) bool { + switch kv.Key { + case exceptionTypeKey, exceptionMessageKey, exceptionStacktraceKey: + hasExceptionAttr = true + } + newRecord.AddAttributes(kv) + return true + }) + + if err := r.Err(); err != nil && !hasExceptionAttr { + addExceptionAttrs(&newRecord, err) + } + + return newRecord +} + +func addExceptionAttrs(r *Record, err error) { + var attrs [2]log.KeyValue + n := 0 + if msg := err.Error(); msg != "" { + if r.attributeCountLimit > 0 && r.attributeCountLimit-r.AttributesLen() < n+1 { + goto flush + } + attrs[n] = log.String(exceptionMessageKey, msg) + n++ + } + if errType := errorType(err); errType != "" { + if r.attributeCountLimit > 0 && r.attributeCountLimit-r.AttributesLen() < n+1 { + goto flush + } + attrs[n] = log.String(exceptionTypeKey, errType) + n++ + } + +flush: + if n > 0 { + r.addAttrs(attrs[:n]) + } +} + +func errorType(err error) string { + if et, ok := err.(interface{ ErrorType() string }); ok { + if s := et.ErrorType(); s != "" { + return s + } + } + + t := reflect.TypeOf(unwrapFmtWrapped(err)) + if t == nil { + return "" + } + + pkg, name := t.PkgPath(), t.Name() + if pkg != "" && name != "" { + return pkg + "." + name + } + + // The type has no package path or name (predeclared, not-defined, + // or alias for a not-defined type). + // + // The type has no package path or name (predeclared, not-defined, + // or alias for a not-defined type). + // + // This is not guaranteed to be unique, but is a best effort. + return t.String() +} + +var fmtWrapErrorType = reflect.TypeOf(fmt.Errorf("wrapped: %w", errors.New("err"))) + +func unwrapFmtWrapped(err error) error { + for reflect.TypeOf(err) == fmtWrapErrorType { + u := errors.Unwrap(err) + if u == nil { + return err // When the wrapped error is nil, use the concrete type of the wrapper. + } + err = u + } + return err +} diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/processor.go b/vendor/go.opentelemetry.io/otel/sdk/log/processor.go new file mode 100644 index 0000000000..ba6319c09b --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/sdk/log/processor.go @@ -0,0 +1,96 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package log // import "go.opentelemetry.io/otel/sdk/log" + +import ( + "context" + + "go.opentelemetry.io/otel/log" + "go.opentelemetry.io/otel/sdk/instrumentation" +) + +// Processor handles the processing of log records. +// +// Any of the Processor's methods may be called concurrently with itself +// or with other methods. It is the responsibility of the Processor to manage +// this concurrency. +type Processor interface { + // Enabled reports whether the Processor will process for the given context + // and param. + // + // The param contains a subset of the information that will be available + // in the Record passed to OnEmit, as defined by EnabledParameters. + // A field being unset in param does not imply the corresponding field + // in the Record passed to OnEmit will be unset. For example, a log bridge + // may be unable to populate all fields in EnabledParameters even though + // they are present on the final Record. + // + // The returned value will be true when the Processor will process for the + // provided context and param, and will be false if the Processor will not + // process. + // + // Implementations that need additional information beyond what is provided + // in param should treat the decision as indeterminate and default to + // returning true, unless they have a specific reason to return false + // (for example, to meet performance or correctness constraints). + // + // Processor implementations are expected to re-evaluate the [Record] passed + // to OnEmit. It is not expected that the caller to OnEmit will + // use the result from Enabled prior to calling OnEmit. + // + // The SDK's Logger.Enabled returns false if all the registered processors + // return false. Otherwise, it returns true. + // + // Implementations of this method need to be safe for a user to call + // concurrently. + Enabled(ctx context.Context, param EnabledParameters) bool + + // OnEmit is called when a Record is emitted. + // + // OnEmit will be called independent of Enabled. Implementations need to + // validate the arguments themselves before processing. + // + // Implementations should not stop processing a Record solely because the + // context is canceled. + // + // Any retry or recovery logic needed by the Processor must be handled + // inside this function. The SDK does not implement any retry logic. + // Errors returned by this function are treated as unrecoverable by the SDK + // and will be reported to a configured error Handler. + // + // The SDK invokes the processors sequentially in the same order as + // they were registered using WithProcessor. + // Implementations may synchronously modify the record so that the changes + // are visible in the next registered processor. + // + // Note that Record is not concurrent safe. Therefore, asynchronous + // processing may cause race conditions. Use Record.Clone + // to create a copy that shares no state with the original. + OnEmit(ctx context.Context, record *Record) error + + // Shutdown is called when the SDK shuts down. Any cleanup or release of + // resources held by the Processor (and any underlying Exporter) should be + // done in this call. + // + // The deadline or cancellation of the passed context must be honored. An + // appropriate error should be returned in these situations. + // + // After Shutdown is called, calls to OnEmit, Shutdown, or ForceFlush + // should perform no operation and return nil error. + Shutdown(ctx context.Context) error + + // ForceFlush exports log records to the configured Exporter that have not yet + // been exported. + // + // The deadline or cancellation of the passed context must be honored. An + // appropriate error should be returned in these situations. + ForceFlush(ctx context.Context) error +} + +// EnabledParameters represents payload for [Processor]'s Enabled method. +type EnabledParameters struct { + InstrumentationScope instrumentation.Scope + Severity log.Severity + EventName string +} diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/provider.go b/vendor/go.opentelemetry.io/otel/sdk/log/provider.go new file mode 100644 index 0000000000..4ac799bfd2 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/sdk/log/provider.go @@ -0,0 +1,276 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package log // import "go.opentelemetry.io/otel/sdk/log" + +import ( + "context" + "errors" + "sync" + "sync/atomic" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/internal/global" + "go.opentelemetry.io/otel/log" + "go.opentelemetry.io/otel/log/embedded" + "go.opentelemetry.io/otel/log/noop" + "go.opentelemetry.io/otel/sdk/instrumentation" + "go.opentelemetry.io/otel/sdk/resource" +) + +const ( + defaultAttrCntLim = 128 + defaultAttrValLenLim = -1 + + envarAttrCntLim = "OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT" + envarAttrValLenLim = "OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT" +) + +type providerConfig struct { + resource *resource.Resource + processors []Processor + attrCntLim setting[int] + attrValLenLim setting[int] + allowDupKeys setting[bool] +} + +type experimentalOption interface { + Experimental() +} + +func newProviderConfig(opts []LoggerProviderOption) providerConfig { + var c providerConfig + for _, opt := range opts { + if _, ok := opt.(experimentalOption); ok { + continue + } + c = opt.apply(c) + } + + if c.resource == nil { + c.resource = resource.Default() + } + + c.attrCntLim = c.attrCntLim.Resolve( + getenv[int](envarAttrCntLim), + fallback[int](defaultAttrCntLim), + ) + + c.attrValLenLim = c.attrValLenLim.Resolve( + getenv[int](envarAttrValLenLim), + fallback[int](defaultAttrValLenLim), + ) + + return c +} + +// LoggerProvider handles the creation and coordination of Loggers. All Loggers +// created by a LoggerProvider will be associated with the same Resource. +type LoggerProvider struct { + embedded.LoggerProvider + + resource *resource.Resource + processors []Processor + attributeCountLimit int + attributeValueLengthLimit int + allowDupKeys bool + + loggersMu sync.Mutex + loggers map[instrumentation.Scope]*logger + + stopped atomic.Bool + + noCmp [0]func() //nolint: unused // This is indeed used. +} + +// Compile-time check LoggerProvider implements log.LoggerProvider. +var _ log.LoggerProvider = (*LoggerProvider)(nil) + +// NewLoggerProvider returns a new and configured LoggerProvider. +// +// By default, the returned LoggerProvider is configured with the default +// Resource and no Processors. Processors cannot be added after a LoggerProvider is +// created. This means the returned LoggerProvider, one created with no +// Processors, will perform no operations. +func NewLoggerProvider(opts ...LoggerProviderOption) *LoggerProvider { + cfg := newProviderConfig(opts) + return &LoggerProvider{ + resource: cfg.resource, + processors: cfg.processors, + attributeCountLimit: cfg.attrCntLim.Value, + attributeValueLengthLimit: cfg.attrValLenLim.Value, + allowDupKeys: cfg.allowDupKeys.Value, + } +} + +// Logger returns a new [log.Logger] with the provided name and configuration. +// +// If p is shut down, a [noop.Logger] instance is returned. +// +// This method can be called concurrently. +func (p *LoggerProvider) Logger(name string, opts ...log.LoggerOption) log.Logger { + if name == "" { + global.Warn("Invalid Logger name.", "name", name) + } + + if p.stopped.Load() { + return noop.NewLoggerProvider().Logger(name, opts...) + } + + cfg := log.NewLoggerConfig(opts...) + scope := instrumentation.Scope{ + Name: name, + Version: cfg.InstrumentationVersion(), + SchemaURL: cfg.SchemaURL(), + Attributes: cfg.InstrumentationAttributes(), + } + + p.loggersMu.Lock() + defer p.loggersMu.Unlock() + + if p.loggers == nil { + l := newLogger(p, scope) + p.loggers = map[instrumentation.Scope]*logger{scope: l} + return l + } + + l, ok := p.loggers[scope] + if !ok { + l = newLogger(p, scope) + p.loggers[scope] = l + } + + return l +} + +// Shutdown shuts down the provider and all processors. +// +// This method can be called concurrently. +func (p *LoggerProvider) Shutdown(ctx context.Context) error { + stopped := p.stopped.Swap(true) + if stopped { + return nil + } + + var err error + for _, p := range p.processors { + err = errors.Join(err, p.Shutdown(ctx)) + } + return err +} + +// ForceFlush flushes all processors. +// +// This method can be called concurrently. +func (p *LoggerProvider) ForceFlush(ctx context.Context) error { + if p.stopped.Load() { + return nil + } + + var err error + for _, p := range p.processors { + err = errors.Join(err, p.ForceFlush(ctx)) + } + return err +} + +// LoggerProviderOption applies a configuration option value to a LoggerProvider. +type LoggerProviderOption interface { + apply(providerConfig) providerConfig +} + +type loggerProviderOptionFunc func(providerConfig) providerConfig + +func (fn loggerProviderOptionFunc) apply(c providerConfig) providerConfig { + return fn(c) +} + +// WithResource associates a Resource with a LoggerProvider. This Resource +// represents the entity producing telemetry and is associated with all Loggers +// the LoggerProvider will create. +// +// By default, if this Option is not used, the default Resource from the +// go.opentelemetry.io/otel/sdk/resource package will be used. +func WithResource(res *resource.Resource) LoggerProviderOption { + return loggerProviderOptionFunc(func(cfg providerConfig) providerConfig { + var err error + cfg.resource, err = resource.Merge(resource.Environment(), res) + if err != nil { + otel.Handle(err) + } + return cfg + }) +} + +// WithProcessor associates Processor with a LoggerProvider. +// +// By default, if this option is not used, the LoggerProvider will perform no +// operations; no data will be exported without a processor. +// +// The SDK invokes the processors sequentially in the same order as they were +// registered. +// +// For production, use [NewBatchProcessor] to batch log records before they are exported. +// For testing and debugging, use [NewSimpleProcessor] to synchronously export log records. +func WithProcessor(processor Processor) LoggerProviderOption { + return loggerProviderOptionFunc(func(cfg providerConfig) providerConfig { + cfg.processors = append(cfg.processors, processor) + return cfg + }) +} + +// WithAttributeCountLimit sets the maximum allowed log record attribute count. +// Any attribute added to a log record once this limit is reached will be dropped. +// +// Setting this to zero means no attributes will be recorded. +// +// Setting this to a negative value means no limit is applied. +// +// If the OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT environment variable is set, +// and this option is not passed, that variable value will be used. +// +// By default, if an environment variable is not set, and this option is not +// passed, 128 will be used. +func WithAttributeCountLimit(limit int) LoggerProviderOption { + return loggerProviderOptionFunc(func(cfg providerConfig) providerConfig { + cfg.attrCntLim = newSetting(limit) + return cfg + }) +} + +// WithAttributeValueLengthLimit sets the maximum allowed attribute value length. +// +// This limit only applies to string, string slice, and byte slice attribute values. +// Strings and byte slices longer than this value will be truncated to this length. +// +// Setting this to a negative value means no limit is applied. +// +// If the OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT environment variable is set, +// and this option is not passed, that variable value will be used. +// +// By default, if an environment variable is not set, and this option is not +// passed, no limit (-1) will be used. +func WithAttributeValueLengthLimit(limit int) LoggerProviderOption { + return loggerProviderOptionFunc(func(cfg providerConfig) providerConfig { + cfg.attrValLenLim = newSetting(limit) + return cfg + }) +} + +// WithAllowKeyDuplication sets whether deduplication is skipped for log attributes or other key-value collections. +// +// By default, the key-value collections within a log record are deduplicated to comply with the OpenTelemetry Specification. +// Deduplication means that if multiple key–value pairs with the same key are present, only a single pair +// is retained and others are discarded. +// +// Disabling deduplication with this option can improve performance e.g. of adding attributes to the log record. +// +// Note that if you disable deduplication, you are responsible for ensuring that duplicate +// key-value pairs within in a single collection are not emitted, +// or that the telemetry receiver can handle such duplicates. +func WithAllowKeyDuplication() LoggerProviderOption { + return loggerProviderOptionFunc(func(cfg providerConfig) providerConfig { + cfg.allowDupKeys = newSetting(true) + return cfg + }) +} diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/record.go b/vendor/go.opentelemetry.io/otel/sdk/log/record.go new file mode 100644 index 0000000000..80b8bfa1ea --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/sdk/log/record.go @@ -0,0 +1,756 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package log // import "go.opentelemetry.io/otel/sdk/log" + +import ( + "slices" + "strings" + "sync" + "time" + "unicode/utf8" + + "go.opentelemetry.io/otel/internal/global" + "go.opentelemetry.io/otel/log" + "go.opentelemetry.io/otel/sdk/instrumentation" + "go.opentelemetry.io/otel/sdk/resource" + "go.opentelemetry.io/otel/trace" +) + +const ( + // attributesInlineCount is the number of attributes that are efficiently + // stored in an array within a Record. This value is borrowed from slog which + // performed a quantitative survey of log library use and found this value to + // cover 95% of all use-cases (https://go.dev/blog/slog#performance). + attributesInlineCount = 5 + // maxUniqueSize helps reduce peak allocation. + maxUniqueSize = 1028 +) + +var logAttrDropped = sync.OnceFunc(func() { + global.Warn("limit reached: dropping log Record attributes") +}) + +var logKeyValuePairDropped = sync.OnceFunc(func() { + global.Warn("key duplication: dropping key-value pair") +}) + +// uniquePool is a pool of unique attributes used for attributes de-duplication. +var uniquePool = sync.Pool{ + New: func() any { return new([]log.KeyValue) }, +} + +func getUnique() *[]log.KeyValue { + return uniquePool.Get().(*[]log.KeyValue) +} + +func putUnique(v *[]log.KeyValue) { + if cap(*v) <= maxUniqueSize { + clear(*v) + *v = (*v)[:0] + uniquePool.Put(v) + } +} + +// indexPool is a pool of index maps used for attributes de-duplication. +var indexPool = sync.Pool{ + New: func() any { return make(map[string]int) }, +} + +func getIndex() map[string]int { + return indexPool.Get().(map[string]int) +} + +func putIndex(index map[string]int) { + clear(index) + indexPool.Put(index) +} + +// seenPool is a pool of seen keys used for maps de-duplication. +var seenPool = sync.Pool{ + New: func() any { return make(map[string]struct{}) }, +} + +func getSeen() map[string]struct{} { + return seenPool.Get().(map[string]struct{}) +} + +func putSeen(seen map[string]struct{}) { + clear(seen) + seenPool.Put(seen) +} + +// Record is a log record emitted by the Logger. +// A log record with non-empty event name is interpreted as an event record. +// +// Do not create instances of Record on your own in production code. +// You can use [go.opentelemetry.io/otel/sdk/log/logtest.RecordFactory] +// for testing purposes. +type Record struct { + // Do not embed the log.Record. Attributes need to be overwrite-able and + // deep-copying needs to be possible. + + eventName string + timestamp time.Time + observedTimestamp time.Time + severity log.Severity + severityText string + body log.Value + + // The fields below are for optimizing the implementation of Attributes and + // AddAttributes. This design is borrowed from the slog Record type: + // https://cs.opensource.google/go/go/+/refs/tags/go1.22.0:src/log/slog/record.go;l=20 + + // Allocation optimization: an inline array sized to hold + // the majority of log calls (based on examination of open-source + // code). It holds the start of the list of attributes. + front [attributesInlineCount]log.KeyValue + + // The number of attributes in front. + nFront int + + // The list of attributes except for those in front. + // Invariants: + // - len(back) > 0 if nFront == len(front) + // - Unused array elements are zero-ed. Used to detect mistakes. + back []log.KeyValue + + // dropped is the count of attributes that have been dropped when limits + // were reached. + dropped int + + traceID trace.TraceID + spanID trace.SpanID + traceFlags trace.TraceFlags + + // resource represents the entity that collected the log. + resource *resource.Resource + + // scope is the Scope that the Logger was created with. + scope *instrumentation.Scope + + attributeValueLengthLimit int + attributeCountLimit int + + // specifies whether we should deduplicate any key value collections or not + allowDupKeys bool + + noCmp [0]func() //nolint: unused // This is indeed used. +} + +func (r *Record) addDropped(n int) { + r.dropped += n + if n > 0 { + logAttrDropped() + } +} + +// EventName returns the event name. +// A log record with non-empty event name is interpreted as an event record. +func (r *Record) EventName() string { + return r.eventName +} + +// SetEventName sets the event name. +// A log record with non-empty event name is interpreted as an event record. +func (r *Record) SetEventName(s string) { + r.eventName = s +} + +// Timestamp returns the time when the log record occurred. +func (r *Record) Timestamp() time.Time { + return r.timestamp +} + +// SetTimestamp sets the time when the log record occurred. +func (r *Record) SetTimestamp(t time.Time) { + r.timestamp = t +} + +// ObservedTimestamp returns the time when the log record was observed. +func (r *Record) ObservedTimestamp() time.Time { + return r.observedTimestamp +} + +// SetObservedTimestamp sets the time when the log record was observed. +func (r *Record) SetObservedTimestamp(t time.Time) { + r.observedTimestamp = t +} + +// Severity returns the severity of the log record. +func (r *Record) Severity() log.Severity { + return r.severity +} + +// SetSeverity sets the severity level of the log record. +func (r *Record) SetSeverity(level log.Severity) { + r.severity = level +} + +// SeverityText returns severity (also known as log level) text. This is the +// original string representation of the severity as it is known at the source. +func (r *Record) SeverityText() string { + return r.severityText +} + +// SetSeverityText sets severity (also known as log level) text. This is the +// original string representation of the severity as it is known at the source. +func (r *Record) SetSeverityText(text string) { + r.severityText = text +} + +// Body returns the body of the log record. +func (r *Record) Body() log.Value { + return r.body +} + +// SetBody sets the body of the log record. +func (r *Record) SetBody(v log.Value) { + if !r.allowDupKeys { + r.body = r.dedupeBodyCollections(v) + } else { + r.body = v + } +} + +// WalkAttributes walks all attributes the log record holds by calling f for +// each on each [log.KeyValue] in the [Record]. Iteration stops if f returns false. +func (r *Record) WalkAttributes(f func(log.KeyValue) bool) { + for i := 0; i < r.nFront; i++ { + if !f(r.front[i]) { + return + } + } + for _, a := range r.back { + if !f(a) { + return + } + } +} + +// AddAttributes adds attributes to the log record. +// Attributes in attrs will overwrite any attribute already added to r with the same key. +func (r *Record) AddAttributes(attrs ...log.KeyValue) { + n := r.AttributesLen() + if n == 0 { + // Avoid the more complex duplicate map lookups below. + var drop int + if !r.allowDupKeys { + attrs, drop = dedup(attrs) + if drop > 0 { + logKeyValuePairDropped() + } + } + + attrs, drop := head(attrs, r.attributeCountLimit) + r.addDropped(drop) + + r.addAttrs(attrs) + return + } + + if !r.allowDupKeys { + // Use a slice from the pool to avoid modifying the original. + // Note, do not iterate attrs twice by just calling dedup(attrs) here. + unique := getUnique() + defer putUnique(unique) + + // Used to find duplicates between attrs and existing attributes in r. + rIndex := r.attrIndex() + defer putIndex(rIndex) + + // Used to find duplicates within attrs itself. + // The index value is the index of the element in unique. + uIndex := getIndex() + defer putIndex(uIndex) + + dropped := 0 + + // Deduplicate attrs within the scope of all existing attributes. + for _, a := range attrs { + // Last-value-wins for any duplicates in attrs. + idx, found := uIndex[a.Key] + if found { + dropped++ + logKeyValuePairDropped() + (*unique)[idx] = a + continue + } + + idx, found = rIndex[a.Key] + if found { + // New attrs overwrite any existing with the same key. + dropped++ + logKeyValuePairDropped() + if idx < 0 { + r.front[-(idx + 1)] = a + } else { + r.back[idx] = a + } + } else { + // Unique attribute. + (*unique) = append(*unique, a) + uIndex[a.Key] = len(*unique) - 1 + } + } + + if dropped > 0 { + attrs = make([]log.KeyValue, len(*unique)) + copy(attrs, *unique) + } + } + + if r.attributeCountLimit > 0 && n+len(attrs) > r.attributeCountLimit { + // Truncate the now unique attributes to comply with limit. + // + // Do not use head(attrs, r.attributeCountLimit - n) here. If + // (r.attributeCountLimit - n) <= 0 attrs needs to be emptied. + last := max(0, r.attributeCountLimit-n) + r.addDropped(len(attrs) - last) + attrs = attrs[:last] + } + + r.addAttrs(attrs) +} + +// attrIndex returns an index map for all attributes in the Record r. The index +// maps the attribute key to location the attribute is stored. If the value is +// < 0 then -(value + 1) (e.g. -1 -> 0, -2 -> 1, -3 -> 2) represents the index +// in r.nFront. Otherwise, the index is the exact index of r.back. +// +// The returned index is taken from the indexPool. It is the callers +// responsibility to return the index to that pool (putIndex) when done. +func (r *Record) attrIndex() map[string]int { + index := getIndex() + for i := 0; i < r.nFront; i++ { + key := r.front[i].Key + index[key] = -i - 1 // stored in front: negative index. + } + for i := 0; i < len(r.back); i++ { + key := r.back[i].Key + index[key] = i // stored in back: positive index. + } + return index +} + +// addAttrs adds attrs to the Record r. This does not validate any limits or +// duplication of attributes, these tasks are left to the caller to handle +// prior to calling. +func (r *Record) addAttrs(attrs []log.KeyValue) { + var i int + for i = 0; i < len(attrs) && r.nFront < len(r.front); i++ { + a := attrs[i] + r.front[r.nFront] = r.applyAttrLimitsAndDedup(a) + r.nFront++ + } + + // Make a copy to avoid modifying the original. + j := len(r.back) + r.back = slices.Grow(r.back, len(attrs[i:])) + r.back = append(r.back, attrs[i:]...) + for i, a := range r.back[j:] { + r.back[i+j] = r.applyAttrLimitsAndDedup(a) + } +} + +// SetAttributes sets (and overrides) attributes to the log record. +func (r *Record) SetAttributes(attrs ...log.KeyValue) { + var drop int + r.dropped = 0 + if !r.allowDupKeys { + attrs, drop = dedup(attrs) + if drop > 0 { + logKeyValuePairDropped() + } + } + + attrs, drop = head(attrs, r.attributeCountLimit) + r.addDropped(drop) + + r.nFront = 0 + var i int + for i = 0; i < len(attrs) && r.nFront < len(r.front); i++ { + a := attrs[i] + r.front[r.nFront] = r.applyAttrLimitsAndDedup(a) + r.nFront++ + } + + r.back = slices.Clone(attrs[i:]) + for i, a := range r.back { + r.back[i] = r.applyAttrLimitsAndDedup(a) + } +} + +// head returns the first n values of kvs along with the number of elements +// dropped. If n is less than or equal to zero, kvs is returned with 0. +func head(kvs []log.KeyValue, n int) (out []log.KeyValue, dropped int) { + if n > 0 && len(kvs) > n { + return kvs[:n], len(kvs) - n + } + return kvs, 0 +} + +// dedup deduplicates kvs front-to-back with the last value saved. +func dedup(kvs []log.KeyValue) (unique []log.KeyValue, dropped int) { + if len(kvs) <= 1 { + return kvs, 0 // No deduplication needed. + } + + index := getIndex() + defer putIndex(index) + u := getUnique() + defer putUnique(u) + for _, a := range kvs { + idx, found := index[a.Key] + if found { + dropped++ + (*u)[idx] = a + } else { + *u = append(*u, a) + index[a.Key] = len(*u) - 1 + } + } + + if dropped == 0 { + return kvs, 0 + } + + unique = make([]log.KeyValue, len(*u)) + copy(unique, *u) + return unique, dropped +} + +// AttributesLen returns the number of attributes in the log record. +func (r *Record) AttributesLen() int { + return r.nFront + len(r.back) +} + +// DroppedAttributes returns the number of attributes dropped due to limits +// being reached. +func (r *Record) DroppedAttributes() int { + return r.dropped +} + +// TraceID returns the trace ID or empty array. +func (r *Record) TraceID() trace.TraceID { + return r.traceID +} + +// SetTraceID sets the trace ID. +func (r *Record) SetTraceID(id trace.TraceID) { + r.traceID = id +} + +// SpanID returns the span ID or empty array. +func (r *Record) SpanID() trace.SpanID { + return r.spanID +} + +// SetSpanID sets the span ID. +func (r *Record) SetSpanID(id trace.SpanID) { + r.spanID = id +} + +// TraceFlags returns the trace flags. +func (r *Record) TraceFlags() trace.TraceFlags { + return r.traceFlags +} + +// SetTraceFlags sets the trace flags. +func (r *Record) SetTraceFlags(flags trace.TraceFlags) { + r.traceFlags = flags +} + +// Resource returns the entity that collected the log. +func (r *Record) Resource() *resource.Resource { + return r.resource +} + +// InstrumentationScope returns the scope that the Logger was created with. +func (r *Record) InstrumentationScope() instrumentation.Scope { + if r.scope == nil { + return instrumentation.Scope{} + } + return *r.scope +} + +// Clone returns a copy of the record with no shared state. The original record +// and the clone can both be modified without interfering with each other. +func (r *Record) Clone() Record { + res := *r + res.back = slices.Clone(r.back) + return res +} + +func (r *Record) applyAttrLimitsAndDedup(attr log.KeyValue) log.KeyValue { + attr.Value = r.applyValueLimitsAndDedup(attr.Value) + return attr +} + +func (r *Record) applyValueLimitsAndDedup(val log.Value) log.Value { + switch val.Kind() { + case log.KindString: + s := val.AsString() + if r.attributeValueLengthLimit >= 0 && len(s) > r.attributeValueLengthLimit { + val = log.StringValue(truncate(r.attributeValueLengthLimit, s)) + } + case log.KindSlice: + sl := val.AsSlice() + + // First check if any limits need to be applied. + if slices.ContainsFunc(sl, r.needsValueLimitsOrDedup) { + // Create a new slice to avoid modifying the original. + newSl := make([]log.Value, len(sl)) + for i, item := range sl { + newSl[i] = r.applyValueLimitsAndDedup(item) + } + val = log.SliceValue(newSl...) + } + case log.KindBytes: + bs := val.AsBytes() + if r.attributeValueLengthLimit >= 0 && len(bs) > r.attributeValueLengthLimit { + val = log.BytesValue(bs[:r.attributeValueLengthLimit]) + } + case log.KindMap: + kvs := val.AsMap() + var newKvs []log.KeyValue + var dropped int + + if !r.allowDupKeys { + // Deduplicate then truncate. + // Do not do at the same time to avoid wasted truncation operations. + newKvs, dropped = dedup(kvs) + if dropped > 0 { + logKeyValuePairDropped() + } + } else { + newKvs = kvs + } + + // Check if any attribute limits need to be applied. + needsChange := false + if dropped > 0 { + needsChange = true // Already changed by dedup. + } else { + for _, kv := range newKvs { + if r.needsValueLimitsOrDedup(kv.Value) { + needsChange = true + break + } + } + } + + if needsChange { + // Only create new slice if changes are needed. + if dropped == 0 { + // Make a copy to avoid modifying the original. + newKvs = make([]log.KeyValue, len(kvs)) + copy(newKvs, kvs) + } + + for i := range newKvs { + newKvs[i] = r.applyAttrLimitsAndDedup(newKvs[i]) + } + val = log.MapValue(newKvs...) + } + } + return val +} + +// needsValueLimitsOrDedup checks if a value would be modified by applyValueLimitsAndDedup. +func (r *Record) needsValueLimitsOrDedup(val log.Value) bool { + switch val.Kind() { + case log.KindString: + return r.attributeValueLengthLimit >= 0 && len(val.AsString()) > r.attributeValueLengthLimit + case log.KindSlice: + if slices.ContainsFunc(val.AsSlice(), r.needsValueLimitsOrDedup) { + return true + } + case log.KindBytes: + bs := val.AsBytes() + if r.attributeValueLengthLimit >= 0 && len(bs) > r.attributeValueLengthLimit { + return true + } + case log.KindMap: + kvs := val.AsMap() + if !r.allowDupKeys && len(kvs) > 1 { + // Check for duplicates. + hasDuplicates := func() bool { + seen := getSeen() + defer putSeen(seen) + for _, kv := range kvs { + if _, ok := seen[kv.Key]; ok { + return true + } + seen[kv.Key] = struct{}{} + } + return false + }() + if hasDuplicates { + return true + } + } + for _, kv := range kvs { + if r.needsValueLimitsOrDedup(kv.Value) { + return true + } + } + } + return false +} + +func (r *Record) dedupeBodyCollections(val log.Value) log.Value { + switch val.Kind() { + case log.KindSlice: + sl := val.AsSlice() + + // Check if any nested values need deduplication. + if slices.ContainsFunc(sl, r.needsBodyDedup) { + // Create a new slice to avoid modifying the original. + newSl := make([]log.Value, len(sl)) + for i, item := range sl { + newSl[i] = r.dedupeBodyCollections(item) + } + val = log.SliceValue(newSl...) + } + + case log.KindMap: + kvs := val.AsMap() + newKvs, dropped := dedup(kvs) + + // Check if any nested values need deduplication. + needsValueChange := false + for _, kv := range newKvs { + if r.needsBodyDedup(kv.Value) { + needsValueChange = true + break + } + } + + if dropped > 0 || needsValueChange { + // Only create new value if changes are needed. + if dropped == 0 { + // Make a copy to avoid modifying the original. + newKvs = make([]log.KeyValue, len(kvs)) + copy(newKvs, kvs) + } + + for i := range newKvs { + newKvs[i].Value = r.dedupeBodyCollections(newKvs[i].Value) + } + val = log.MapValue(newKvs...) + } + } + return val +} + +// needsBodyDedup checks if a value would be modified by dedupeBodyCollections. +func (r *Record) needsBodyDedup(val log.Value) bool { + switch val.Kind() { + case log.KindSlice: + if slices.ContainsFunc(val.AsSlice(), r.needsBodyDedup) { + return true + } + case log.KindMap: + kvs := val.AsMap() + if len(kvs) > 1 { + // Check for duplicates. + hasDuplicates := func() bool { + seen := getSeen() + defer putSeen(seen) + for _, kv := range kvs { + if _, ok := seen[kv.Key]; ok { + return true + } + seen[kv.Key] = struct{}{} + } + return false + }() + if hasDuplicates { + return true + } + } + for _, kv := range kvs { + if r.needsBodyDedup(kv.Value) { + return true + } + } + } + return false +} + +// truncate returns a truncated version of s such that it contains less than +// the limit number of characters. Truncation is applied by returning the limit +// number of valid characters contained in s. +// +// If limit is negative, it returns the original string. +// +// UTF-8 is supported. When truncating, all invalid characters are dropped +// before applying truncation. +// +// If s already contains less than the limit number of bytes, it is returned +// unchanged. No invalid characters are removed. +func truncate(limit int, s string) string { + // This prioritize performance in the following order based on the most + // common expected use-cases. + // + // - Short values less than the default limit (128). + // - Strings with valid encodings that exceed the limit. + // - No limit. + // - Strings with invalid encodings that exceed the limit. + if limit < 0 || len(s) <= limit { + return s + } + + // Optimistically, assume all valid UTF-8. + var b strings.Builder + count := 0 + for i, c := range s { + if c != utf8.RuneError { + count++ + if count > limit { + return s[:i] + } + continue + } + + _, size := utf8.DecodeRuneInString(s[i:]) + if size == 1 { + // Invalid encoding. + b.Grow(len(s) - 1) + _, _ = b.WriteString(s[:i]) + s = s[i:] + break + } + } + + // Fast-path, no invalid input. + if b.Cap() == 0 { + return s + } + + // Truncate while validating UTF-8. + for i := 0; i < len(s) && count < limit; { + c := s[i] + if c < utf8.RuneSelf { + // Optimization for single byte runes (common case). + _ = b.WriteByte(c) + i++ + count++ + continue + } + + _, size := utf8.DecodeRuneInString(s[i:]) + if size == 1 { + // We checked for all 1-byte runes above, this is a RuneError. + i++ + continue + } + + _, _ = b.WriteString(s[i : i+size]) + i += size + count++ + } + + return b.String() +} diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/ring.go b/vendor/go.opentelemetry.io/otel/sdk/log/ring.go new file mode 100644 index 0000000000..5e84cb1645 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/sdk/log/ring.go @@ -0,0 +1,82 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package log // import "go.opentelemetry.io/otel/sdk/log" + +// A ring is an element of a circular list, or ring. Rings do not have a +// beginning or end; a pointer to any ring element serves as reference to the +// entire ring. Empty rings are represented as nil ring pointers. The zero +// value for a ring is a one-element ring with a nil Value. +// +// This is copied from the "container/ring" package. It uses a Record type for +// Value instead of any to avoid allocations. +type ring struct { + next, prev *ring + Value Record +} + +func (r *ring) init() *ring { + r.next = r + r.prev = r + return r +} + +// Next returns the next ring element. r must not be empty. +func (r *ring) Next() *ring { + if r.next == nil { + return r.init() + } + return r.next +} + +// Prev returns the previous ring element. r must not be empty. +func (r *ring) Prev() *ring { + if r.next == nil { + return r.init() + } + return r.prev +} + +// newRing creates a ring of n elements. +func newRing(n int) *ring { + if n <= 0 { + return nil + } + r := new(ring) + p := r + for i := 1; i < n; i++ { + p.next = &ring{prev: p} + p = p.next + } + p.next = r + r.prev = p + return r +} + +// Len computes the number of elements in ring r. It executes in time +// proportional to the number of elements. +func (r *ring) Len() int { + n := 0 + if r != nil { + n = 1 + for p := r.Next(); p != r; p = p.next { + n++ + } + } + return n +} + +// Do calls function f on each element of the ring, in forward order. The +// behavior of Do is undefined if f changes *r. +func (r *ring) Do(f func(Record)) { + if r != nil { + f(r.Value) + for p := r.Next(); p != r; p = p.next { + f(p.Value) + } + } +} diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/setting.go b/vendor/go.opentelemetry.io/otel/sdk/log/setting.go new file mode 100644 index 0000000000..72107a9e58 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/sdk/log/setting.go @@ -0,0 +1,119 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package log // import "go.opentelemetry.io/otel/sdk/log" + +import ( + "fmt" + "os" + "strconv" + "time" + + "go.opentelemetry.io/otel" +) + +// setting is a configuration setting value. +type setting[T any] struct { + Value T + Set bool +} + +// newSetting returns a new [setting] with the value set. +func newSetting[T any](value T) setting[T] { + return setting[T]{Value: value, Set: true} +} + +// resolver returns an updated setting after applying an resolution operation. +type resolver[T any] func(setting[T]) setting[T] + +// Resolve returns a resolved version of s. +// +// It will apply all the passed fn in the order provided, chaining together the +// return setting to the next input. The setting s is used as the initial +// argument to the first fn. +// +// Each fn needs to validate if it should apply given the Set state of the +// setting. This will not perform any checks on the set state when chaining +// function. +func (s setting[T]) Resolve(fn ...resolver[T]) setting[T] { + for _, f := range fn { + s = f(s) + } + return s +} + +// clampMax returns a resolver that will ensure a setting value is no greater +// than n. If it is, the value is set to n. +func clampMax[T ~int | ~int64](n T) resolver[T] { + return func(s setting[T]) setting[T] { + if s.Value > n { + s.Value = n + } + return s + } +} + +// clearLessThanOne returns a resolver that will clear a setting value and +// change its set state to false if its value is less than 1. +func clearLessThanOne[T ~int | ~int64]() resolver[T] { + return func(s setting[T]) setting[T] { + if s.Value < 1 { + s.Value = 0 + s.Set = false + } + return s + } +} + +// getenv returns a resolver that will apply an integer environment variable +// value associated with key to a setting value. +// +// If the input setting to the resolver is set, the environment variable will +// not be applied. +// +// If the environment variable value associated with key is not an integer, an +// error will be sent to the OTel error handler and the setting will not be +// updated. +// +// If the setting value is a [time.Duration] type, the environment variable +// will be interpreted as a duration of milliseconds. +func getenv[T ~int | ~int64](key string) resolver[T] { + return func(s setting[T]) setting[T] { + if s.Set { + // Passed, valid, options have precedence. + return s + } + + if v := os.Getenv(key); v != "" { + n, err := strconv.Atoi(v) + if err != nil { + otel.Handle(fmt.Errorf("invalid %s value %s: %w", key, v, err)) + } else { + switch any(s.Value).(type) { + case time.Duration: + // OTel duration envar are in millisecond. + s.Value = T(time.Duration(n) * time.Millisecond) + default: + s.Value = T(n) + } + s.Set = true + } + } + return s + } +} + +// fallback returns a resolve that will set a setting value to val if it is not +// already set. +// +// This is usually passed at the end of a resolver chain to ensure a default is +// applied if the setting has not already been set. +func fallback[T any](val T) resolver[T] { + return func(s setting[T]) setting[T] { + if !s.Set { + s.Value = val + s.Set = true + } + return s + } +} diff --git a/vendor/go.opentelemetry.io/otel/sdk/log/simple.go b/vendor/go.opentelemetry.io/otel/sdk/log/simple.go new file mode 100644 index 0000000000..9772e0c5e6 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/sdk/log/simple.go @@ -0,0 +1,104 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package log // import "go.opentelemetry.io/otel/sdk/log" + +import ( + "context" + "sync" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/sdk/log/internal/observ" +) + +// Compile-time check SimpleProcessor implements Processor. +var _ Processor = (*SimpleProcessor)(nil) + +// SimpleProcessor is an processor that synchronously exports log records. +// +// Use [NewSimpleProcessor] to create a SimpleProcessor. +type SimpleProcessor struct { + mu sync.Mutex + exporter Exporter + inst *observ.SLP + noCmp [0]func() //nolint: unused // This is indeed used. +} + +// NewSimpleProcessor is a simple Processor adapter. +// +// This Processor is not recommended for production use due to its synchronous +// nature, which makes it suitable for testing, debugging, or demonstrating +// other features, but can lead to slow performance and high computational +// overhead. For production environments, it is recommended to use +// [NewBatchProcessor] instead. However, there may be exceptions where certain +// [Exporter] implementations perform better with this Processor. +func NewSimpleProcessor(exporter Exporter, _ ...SimpleProcessorOption) *SimpleProcessor { + slp := &SimpleProcessor{ + exporter: exporter, + } + var err error + slp.inst, err = observ.NewSLP(observ.NextSimpleProcessorID()) + if err != nil { + otel.Handle(err) + } + return slp +} + +var simpleProcRecordsPool = sync.Pool{ + New: func() any { + records := make([]Record, 1) + return &records + }, +} + +// Enabled returns true, indicating this Processor will process all records. +func (*SimpleProcessor) Enabled(context.Context, EnabledParameters) bool { + return true +} + +// OnEmit batches provided log record. +func (s *SimpleProcessor) OnEmit(ctx context.Context, r *Record) (err error) { + if s.exporter == nil { + return nil + } + + s.mu.Lock() + defer s.mu.Unlock() + + records := simpleProcRecordsPool.Get().(*[]Record) + defer func() { + clear(*records) + simpleProcRecordsPool.Put(records) + }() + (*records)[0] = *r + + if s.inst != nil { + defer func() { + s.inst.LogProcessed(ctx, err) + }() + } + return s.exporter.Export(ctx, *records) +} + +// Shutdown shuts down the exporter. +func (s *SimpleProcessor) Shutdown(ctx context.Context) error { + if s.exporter == nil { + return nil + } + + return s.exporter.Shutdown(ctx) +} + +// ForceFlush flushes the exporter. +func (s *SimpleProcessor) ForceFlush(ctx context.Context) error { + if s.exporter == nil { + return nil + } + + return s.exporter.ForceFlush(ctx) +} + +// SimpleProcessorOption applies a configuration to a [SimpleProcessor]. +type SimpleProcessorOption interface { + apply() +} diff --git a/vendor/go.opentelemetry.io/proto/otlp/collector/logs/v1/logs_service.pb.go b/vendor/go.opentelemetry.io/proto/otlp/collector/logs/v1/logs_service.pb.go new file mode 100644 index 0000000000..978efdde93 --- /dev/null +++ b/vendor/go.opentelemetry.io/proto/otlp/collector/logs/v1/logs_service.pb.go @@ -0,0 +1,367 @@ +// Copyright 2020, OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.21.6 +// source: opentelemetry/proto/collector/logs/v1/logs_service.proto + +package v1 + +import ( + v1 "go.opentelemetry.io/proto/otlp/logs/v1" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ExportLogsServiceRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // An array of ResourceLogs. + // For data coming from a single resource this array will typically contain one + // element. Intermediary nodes (such as OpenTelemetry Collector) that receive + // data from multiple origins typically batch the data before forwarding further and + // in that case this array will contain multiple elements. + ResourceLogs []*v1.ResourceLogs `protobuf:"bytes,1,rep,name=resource_logs,json=resourceLogs,proto3" json:"resource_logs,omitempty"` +} + +func (x *ExportLogsServiceRequest) Reset() { + *x = ExportLogsServiceRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_opentelemetry_proto_collector_logs_v1_logs_service_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExportLogsServiceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExportLogsServiceRequest) ProtoMessage() {} + +func (x *ExportLogsServiceRequest) ProtoReflect() protoreflect.Message { + mi := &file_opentelemetry_proto_collector_logs_v1_logs_service_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExportLogsServiceRequest.ProtoReflect.Descriptor instead. +func (*ExportLogsServiceRequest) Descriptor() ([]byte, []int) { + return file_opentelemetry_proto_collector_logs_v1_logs_service_proto_rawDescGZIP(), []int{0} +} + +func (x *ExportLogsServiceRequest) GetResourceLogs() []*v1.ResourceLogs { + if x != nil { + return x.ResourceLogs + } + return nil +} + +type ExportLogsServiceResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The details of a partially successful export request. + // + // If the request is only partially accepted + // (i.e. when the server accepts only parts of the data and rejects the rest) + // the server MUST initialize the `partial_success` field and MUST + // set the `rejected_` with the number of items it rejected. + // + // Servers MAY also make use of the `partial_success` field to convey + // warnings/suggestions to senders even when the request was fully accepted. + // In such cases, the `rejected_` MUST have a value of `0` and + // the `error_message` MUST be non-empty. + // + // A `partial_success` message with an empty value (rejected_ = 0 and + // `error_message` = "") is equivalent to it not being set/present. Senders + // SHOULD interpret it the same way as in the full success case. + PartialSuccess *ExportLogsPartialSuccess `protobuf:"bytes,1,opt,name=partial_success,json=partialSuccess,proto3" json:"partial_success,omitempty"` +} + +func (x *ExportLogsServiceResponse) Reset() { + *x = ExportLogsServiceResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_opentelemetry_proto_collector_logs_v1_logs_service_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExportLogsServiceResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExportLogsServiceResponse) ProtoMessage() {} + +func (x *ExportLogsServiceResponse) ProtoReflect() protoreflect.Message { + mi := &file_opentelemetry_proto_collector_logs_v1_logs_service_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExportLogsServiceResponse.ProtoReflect.Descriptor instead. +func (*ExportLogsServiceResponse) Descriptor() ([]byte, []int) { + return file_opentelemetry_proto_collector_logs_v1_logs_service_proto_rawDescGZIP(), []int{1} +} + +func (x *ExportLogsServiceResponse) GetPartialSuccess() *ExportLogsPartialSuccess { + if x != nil { + return x.PartialSuccess + } + return nil +} + +type ExportLogsPartialSuccess struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The number of rejected log records. + // + // A `rejected_` field holding a `0` value indicates that the + // request was fully accepted. + RejectedLogRecords int64 `protobuf:"varint,1,opt,name=rejected_log_records,json=rejectedLogRecords,proto3" json:"rejected_log_records,omitempty"` + // A developer-facing human-readable message in English. It should be used + // either to explain why the server rejected parts of the data during a partial + // success or to convey warnings/suggestions during a full success. The message + // should offer guidance on how users can address such issues. + // + // error_message is an optional field. An error_message with an empty value + // is equivalent to it not being set. + ErrorMessage string `protobuf:"bytes,2,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` +} + +func (x *ExportLogsPartialSuccess) Reset() { + *x = ExportLogsPartialSuccess{} + if protoimpl.UnsafeEnabled { + mi := &file_opentelemetry_proto_collector_logs_v1_logs_service_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExportLogsPartialSuccess) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExportLogsPartialSuccess) ProtoMessage() {} + +func (x *ExportLogsPartialSuccess) ProtoReflect() protoreflect.Message { + mi := &file_opentelemetry_proto_collector_logs_v1_logs_service_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExportLogsPartialSuccess.ProtoReflect.Descriptor instead. +func (*ExportLogsPartialSuccess) Descriptor() ([]byte, []int) { + return file_opentelemetry_proto_collector_logs_v1_logs_service_proto_rawDescGZIP(), []int{2} +} + +func (x *ExportLogsPartialSuccess) GetRejectedLogRecords() int64 { + if x != nil { + return x.RejectedLogRecords + } + return 0 +} + +func (x *ExportLogsPartialSuccess) GetErrorMessage() string { + if x != nil { + return x.ErrorMessage + } + return "" +} + +var File_opentelemetry_proto_collector_logs_v1_logs_service_proto protoreflect.FileDescriptor + +var file_opentelemetry_proto_collector_logs_v1_logs_service_proto_rawDesc = []byte{ + 0x0a, 0x38, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2f, + 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x6c, 0x6f, 0x67, 0x73, 0x5f, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x25, 0x6f, 0x70, 0x65, 0x6e, + 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x76, + 0x31, 0x1a, 0x26, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x6c, + 0x6f, 0x67, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x6a, 0x0a, 0x18, 0x45, 0x78, 0x70, + 0x6f, 0x72, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4e, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x5f, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6f, + 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x22, 0x85, 0x01, 0x0a, 0x19, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, + 0x4c, 0x6f, 0x67, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x68, 0x0a, 0x0f, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, + 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3f, 0x2e, 0x6f, + 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x6c, 0x6f, 0x67, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x50, + 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x52, 0x0e, 0x70, + 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0x71, 0x0a, + 0x18, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x50, 0x61, 0x72, 0x74, 0x69, + 0x61, 0x6c, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x72, 0x65, 0x6a, + 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x6c, 0x6f, 0x67, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x65, + 0x64, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x65, + 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x32, 0x9d, 0x01, 0x0a, 0x0b, 0x4c, 0x6f, 0x67, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x12, 0x8d, 0x01, 0x0a, 0x06, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x3f, 0x2e, 0x6f, 0x70, + 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x6c, 0x6f, 0x67, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x40, 0x2e, 0x6f, + 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x6c, 0x6f, 0x67, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x42, 0x98, 0x01, 0x0a, 0x28, 0x69, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, + 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, + 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x42, 0x10, 0x4c, + 0x6f, 0x67, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, + 0x01, 0x5a, 0x30, 0x67, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, + 0x74, 0x72, 0x79, 0x2e, 0x69, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6f, 0x74, 0x6c, + 0x70, 0x2f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2f, 0x6c, 0x6f, 0x67, 0x73, + 0x2f, 0x76, 0x31, 0xaa, 0x02, 0x25, 0x4f, 0x70, 0x65, 0x6e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, + 0x74, 0x72, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x73, 0x2e, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, +} + +var ( + file_opentelemetry_proto_collector_logs_v1_logs_service_proto_rawDescOnce sync.Once + file_opentelemetry_proto_collector_logs_v1_logs_service_proto_rawDescData = file_opentelemetry_proto_collector_logs_v1_logs_service_proto_rawDesc +) + +func file_opentelemetry_proto_collector_logs_v1_logs_service_proto_rawDescGZIP() []byte { + file_opentelemetry_proto_collector_logs_v1_logs_service_proto_rawDescOnce.Do(func() { + file_opentelemetry_proto_collector_logs_v1_logs_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_opentelemetry_proto_collector_logs_v1_logs_service_proto_rawDescData) + }) + return file_opentelemetry_proto_collector_logs_v1_logs_service_proto_rawDescData +} + +var file_opentelemetry_proto_collector_logs_v1_logs_service_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_opentelemetry_proto_collector_logs_v1_logs_service_proto_goTypes = []interface{}{ + (*ExportLogsServiceRequest)(nil), // 0: opentelemetry.proto.collector.logs.v1.ExportLogsServiceRequest + (*ExportLogsServiceResponse)(nil), // 1: opentelemetry.proto.collector.logs.v1.ExportLogsServiceResponse + (*ExportLogsPartialSuccess)(nil), // 2: opentelemetry.proto.collector.logs.v1.ExportLogsPartialSuccess + (*v1.ResourceLogs)(nil), // 3: opentelemetry.proto.logs.v1.ResourceLogs +} +var file_opentelemetry_proto_collector_logs_v1_logs_service_proto_depIdxs = []int32{ + 3, // 0: opentelemetry.proto.collector.logs.v1.ExportLogsServiceRequest.resource_logs:type_name -> opentelemetry.proto.logs.v1.ResourceLogs + 2, // 1: opentelemetry.proto.collector.logs.v1.ExportLogsServiceResponse.partial_success:type_name -> opentelemetry.proto.collector.logs.v1.ExportLogsPartialSuccess + 0, // 2: opentelemetry.proto.collector.logs.v1.LogsService.Export:input_type -> opentelemetry.proto.collector.logs.v1.ExportLogsServiceRequest + 1, // 3: opentelemetry.proto.collector.logs.v1.LogsService.Export:output_type -> opentelemetry.proto.collector.logs.v1.ExportLogsServiceResponse + 3, // [3:4] is the sub-list for method output_type + 2, // [2:3] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_opentelemetry_proto_collector_logs_v1_logs_service_proto_init() } +func file_opentelemetry_proto_collector_logs_v1_logs_service_proto_init() { + if File_opentelemetry_proto_collector_logs_v1_logs_service_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_opentelemetry_proto_collector_logs_v1_logs_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExportLogsServiceRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_opentelemetry_proto_collector_logs_v1_logs_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExportLogsServiceResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_opentelemetry_proto_collector_logs_v1_logs_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExportLogsPartialSuccess); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_opentelemetry_proto_collector_logs_v1_logs_service_proto_rawDesc, + NumEnums: 0, + NumMessages: 3, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_opentelemetry_proto_collector_logs_v1_logs_service_proto_goTypes, + DependencyIndexes: file_opentelemetry_proto_collector_logs_v1_logs_service_proto_depIdxs, + MessageInfos: file_opentelemetry_proto_collector_logs_v1_logs_service_proto_msgTypes, + }.Build() + File_opentelemetry_proto_collector_logs_v1_logs_service_proto = out.File + file_opentelemetry_proto_collector_logs_v1_logs_service_proto_rawDesc = nil + file_opentelemetry_proto_collector_logs_v1_logs_service_proto_goTypes = nil + file_opentelemetry_proto_collector_logs_v1_logs_service_proto_depIdxs = nil +} diff --git a/vendor/go.opentelemetry.io/proto/otlp/collector/logs/v1/logs_service.pb.gw.go b/vendor/go.opentelemetry.io/proto/otlp/collector/logs/v1/logs_service.pb.gw.go new file mode 100644 index 0000000000..d34f32d91b --- /dev/null +++ b/vendor/go.opentelemetry.io/proto/otlp/collector/logs/v1/logs_service.pb.gw.go @@ -0,0 +1,171 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: opentelemetry/proto/collector/logs/v1/logs_service.proto + +/* +Package v1 is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package v1 + +import ( + "context" + "io" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = metadata.Join + +func request_LogsService_Export_0(ctx context.Context, marshaler runtime.Marshaler, client LogsServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ExportLogsServiceRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.Export(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_LogsService_Export_0(ctx context.Context, marshaler runtime.Marshaler, server LogsServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ExportLogsServiceRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.Export(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterLogsServiceHandlerServer registers the http handlers for service LogsService to "mux". +// UnaryRPC :call LogsServiceServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterLogsServiceHandlerFromEndpoint instead. +func RegisterLogsServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server LogsServiceServer) error { + + mux.Handle("POST", pattern_LogsService_Export_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/opentelemetry.proto.collector.logs.v1.LogsService/Export", runtime.WithHTTPPathPattern("/v1/logs")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_LogsService_Export_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_LogsService_Export_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterLogsServiceHandlerFromEndpoint is same as RegisterLogsServiceHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterLogsServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterLogsServiceHandler(ctx, mux, conn) +} + +// RegisterLogsServiceHandler registers the http handlers for service LogsService to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterLogsServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterLogsServiceHandlerClient(ctx, mux, NewLogsServiceClient(conn)) +} + +// RegisterLogsServiceHandlerClient registers the http handlers for service LogsService +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "LogsServiceClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "LogsServiceClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "LogsServiceClient" to call the correct interceptors. +func RegisterLogsServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client LogsServiceClient) error { + + mux.Handle("POST", pattern_LogsService_Export_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/opentelemetry.proto.collector.logs.v1.LogsService/Export", runtime.WithHTTPPathPattern("/v1/logs")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_LogsService_Export_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_LogsService_Export_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_LogsService_Export_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "logs"}, "")) +) + +var ( + forward_LogsService_Export_0 = runtime.ForwardResponseMessage +) diff --git a/vendor/go.opentelemetry.io/proto/otlp/collector/logs/v1/logs_service_grpc.pb.go b/vendor/go.opentelemetry.io/proto/otlp/collector/logs/v1/logs_service_grpc.pb.go new file mode 100644 index 0000000000..d1d24fe620 --- /dev/null +++ b/vendor/go.opentelemetry.io/proto/otlp/collector/logs/v1/logs_service_grpc.pb.go @@ -0,0 +1,105 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.1.0 +// - protoc v3.21.6 +// source: opentelemetry/proto/collector/logs/v1/logs_service.proto + +package v1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// LogsServiceClient is the client API for LogsService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type LogsServiceClient interface { + Export(ctx context.Context, in *ExportLogsServiceRequest, opts ...grpc.CallOption) (*ExportLogsServiceResponse, error) +} + +type logsServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewLogsServiceClient(cc grpc.ClientConnInterface) LogsServiceClient { + return &logsServiceClient{cc} +} + +func (c *logsServiceClient) Export(ctx context.Context, in *ExportLogsServiceRequest, opts ...grpc.CallOption) (*ExportLogsServiceResponse, error) { + out := new(ExportLogsServiceResponse) + err := c.cc.Invoke(ctx, "/opentelemetry.proto.collector.logs.v1.LogsService/Export", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// LogsServiceServer is the server API for LogsService service. +// All implementations must embed UnimplementedLogsServiceServer +// for forward compatibility +type LogsServiceServer interface { + Export(context.Context, *ExportLogsServiceRequest) (*ExportLogsServiceResponse, error) + mustEmbedUnimplementedLogsServiceServer() +} + +// UnimplementedLogsServiceServer must be embedded to have forward compatible implementations. +type UnimplementedLogsServiceServer struct { +} + +func (UnimplementedLogsServiceServer) Export(context.Context, *ExportLogsServiceRequest) (*ExportLogsServiceResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Export not implemented") +} +func (UnimplementedLogsServiceServer) mustEmbedUnimplementedLogsServiceServer() {} + +// UnsafeLogsServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to LogsServiceServer will +// result in compilation errors. +type UnsafeLogsServiceServer interface { + mustEmbedUnimplementedLogsServiceServer() +} + +func RegisterLogsServiceServer(s grpc.ServiceRegistrar, srv LogsServiceServer) { + s.RegisterService(&LogsService_ServiceDesc, srv) +} + +func _LogsService_Export_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ExportLogsServiceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LogsServiceServer).Export(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/opentelemetry.proto.collector.logs.v1.LogsService/Export", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LogsServiceServer).Export(ctx, req.(*ExportLogsServiceRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// LogsService_ServiceDesc is the grpc.ServiceDesc for LogsService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var LogsService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "opentelemetry.proto.collector.logs.v1.LogsService", + HandlerType: (*LogsServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Export", + Handler: _LogsService_Export_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "opentelemetry/proto/collector/logs/v1/logs_service.proto", +} diff --git a/vendor/go.opentelemetry.io/proto/otlp/logs/v1/logs.pb.go b/vendor/go.opentelemetry.io/proto/otlp/logs/v1/logs.pb.go new file mode 100644 index 0000000000..ab284c0c0d --- /dev/null +++ b/vendor/go.opentelemetry.io/proto/otlp/logs/v1/logs.pb.go @@ -0,0 +1,870 @@ +// Copyright 2020, OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.21.6 +// source: opentelemetry/proto/logs/v1/logs.proto + +package v1 + +import ( + v11 "go.opentelemetry.io/proto/otlp/common/v1" + v1 "go.opentelemetry.io/proto/otlp/resource/v1" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Possible values for LogRecord.SeverityNumber. +type SeverityNumber int32 + +const ( + SeverityNumber_SEVERITY_NUMBER_UNSPECIFIED SeverityNumber = 0 + SeverityNumber_SEVERITY_NUMBER_TRACE SeverityNumber = 1 + SeverityNumber_SEVERITY_NUMBER_TRACE2 SeverityNumber = 2 + SeverityNumber_SEVERITY_NUMBER_TRACE3 SeverityNumber = 3 + SeverityNumber_SEVERITY_NUMBER_TRACE4 SeverityNumber = 4 + SeverityNumber_SEVERITY_NUMBER_DEBUG SeverityNumber = 5 + SeverityNumber_SEVERITY_NUMBER_DEBUG2 SeverityNumber = 6 + SeverityNumber_SEVERITY_NUMBER_DEBUG3 SeverityNumber = 7 + SeverityNumber_SEVERITY_NUMBER_DEBUG4 SeverityNumber = 8 + SeverityNumber_SEVERITY_NUMBER_INFO SeverityNumber = 9 + SeverityNumber_SEVERITY_NUMBER_INFO2 SeverityNumber = 10 + SeverityNumber_SEVERITY_NUMBER_INFO3 SeverityNumber = 11 + SeverityNumber_SEVERITY_NUMBER_INFO4 SeverityNumber = 12 + SeverityNumber_SEVERITY_NUMBER_WARN SeverityNumber = 13 + SeverityNumber_SEVERITY_NUMBER_WARN2 SeverityNumber = 14 + SeverityNumber_SEVERITY_NUMBER_WARN3 SeverityNumber = 15 + SeverityNumber_SEVERITY_NUMBER_WARN4 SeverityNumber = 16 + SeverityNumber_SEVERITY_NUMBER_ERROR SeverityNumber = 17 + SeverityNumber_SEVERITY_NUMBER_ERROR2 SeverityNumber = 18 + SeverityNumber_SEVERITY_NUMBER_ERROR3 SeverityNumber = 19 + SeverityNumber_SEVERITY_NUMBER_ERROR4 SeverityNumber = 20 + SeverityNumber_SEVERITY_NUMBER_FATAL SeverityNumber = 21 + SeverityNumber_SEVERITY_NUMBER_FATAL2 SeverityNumber = 22 + SeverityNumber_SEVERITY_NUMBER_FATAL3 SeverityNumber = 23 + SeverityNumber_SEVERITY_NUMBER_FATAL4 SeverityNumber = 24 +) + +// Enum value maps for SeverityNumber. +var ( + SeverityNumber_name = map[int32]string{ + 0: "SEVERITY_NUMBER_UNSPECIFIED", + 1: "SEVERITY_NUMBER_TRACE", + 2: "SEVERITY_NUMBER_TRACE2", + 3: "SEVERITY_NUMBER_TRACE3", + 4: "SEVERITY_NUMBER_TRACE4", + 5: "SEVERITY_NUMBER_DEBUG", + 6: "SEVERITY_NUMBER_DEBUG2", + 7: "SEVERITY_NUMBER_DEBUG3", + 8: "SEVERITY_NUMBER_DEBUG4", + 9: "SEVERITY_NUMBER_INFO", + 10: "SEVERITY_NUMBER_INFO2", + 11: "SEVERITY_NUMBER_INFO3", + 12: "SEVERITY_NUMBER_INFO4", + 13: "SEVERITY_NUMBER_WARN", + 14: "SEVERITY_NUMBER_WARN2", + 15: "SEVERITY_NUMBER_WARN3", + 16: "SEVERITY_NUMBER_WARN4", + 17: "SEVERITY_NUMBER_ERROR", + 18: "SEVERITY_NUMBER_ERROR2", + 19: "SEVERITY_NUMBER_ERROR3", + 20: "SEVERITY_NUMBER_ERROR4", + 21: "SEVERITY_NUMBER_FATAL", + 22: "SEVERITY_NUMBER_FATAL2", + 23: "SEVERITY_NUMBER_FATAL3", + 24: "SEVERITY_NUMBER_FATAL4", + } + SeverityNumber_value = map[string]int32{ + "SEVERITY_NUMBER_UNSPECIFIED": 0, + "SEVERITY_NUMBER_TRACE": 1, + "SEVERITY_NUMBER_TRACE2": 2, + "SEVERITY_NUMBER_TRACE3": 3, + "SEVERITY_NUMBER_TRACE4": 4, + "SEVERITY_NUMBER_DEBUG": 5, + "SEVERITY_NUMBER_DEBUG2": 6, + "SEVERITY_NUMBER_DEBUG3": 7, + "SEVERITY_NUMBER_DEBUG4": 8, + "SEVERITY_NUMBER_INFO": 9, + "SEVERITY_NUMBER_INFO2": 10, + "SEVERITY_NUMBER_INFO3": 11, + "SEVERITY_NUMBER_INFO4": 12, + "SEVERITY_NUMBER_WARN": 13, + "SEVERITY_NUMBER_WARN2": 14, + "SEVERITY_NUMBER_WARN3": 15, + "SEVERITY_NUMBER_WARN4": 16, + "SEVERITY_NUMBER_ERROR": 17, + "SEVERITY_NUMBER_ERROR2": 18, + "SEVERITY_NUMBER_ERROR3": 19, + "SEVERITY_NUMBER_ERROR4": 20, + "SEVERITY_NUMBER_FATAL": 21, + "SEVERITY_NUMBER_FATAL2": 22, + "SEVERITY_NUMBER_FATAL3": 23, + "SEVERITY_NUMBER_FATAL4": 24, + } +) + +func (x SeverityNumber) Enum() *SeverityNumber { + p := new(SeverityNumber) + *p = x + return p +} + +func (x SeverityNumber) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (SeverityNumber) Descriptor() protoreflect.EnumDescriptor { + return file_opentelemetry_proto_logs_v1_logs_proto_enumTypes[0].Descriptor() +} + +func (SeverityNumber) Type() protoreflect.EnumType { + return &file_opentelemetry_proto_logs_v1_logs_proto_enumTypes[0] +} + +func (x SeverityNumber) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use SeverityNumber.Descriptor instead. +func (SeverityNumber) EnumDescriptor() ([]byte, []int) { + return file_opentelemetry_proto_logs_v1_logs_proto_rawDescGZIP(), []int{0} +} + +// LogRecordFlags represents constants used to interpret the +// LogRecord.flags field, which is protobuf 'fixed32' type and is to +// be used as bit-fields. Each non-zero value defined in this enum is +// a bit-mask. To extract the bit-field, for example, use an +// expression like: +// +// (logRecord.flags & LOG_RECORD_FLAGS_TRACE_FLAGS_MASK) +// +type LogRecordFlags int32 + +const ( + // The zero value for the enum. Should not be used for comparisons. + // Instead use bitwise "and" with the appropriate mask as shown above. + LogRecordFlags_LOG_RECORD_FLAGS_DO_NOT_USE LogRecordFlags = 0 + // Bits 0-7 are used for trace flags. + LogRecordFlags_LOG_RECORD_FLAGS_TRACE_FLAGS_MASK LogRecordFlags = 255 +) + +// Enum value maps for LogRecordFlags. +var ( + LogRecordFlags_name = map[int32]string{ + 0: "LOG_RECORD_FLAGS_DO_NOT_USE", + 255: "LOG_RECORD_FLAGS_TRACE_FLAGS_MASK", + } + LogRecordFlags_value = map[string]int32{ + "LOG_RECORD_FLAGS_DO_NOT_USE": 0, + "LOG_RECORD_FLAGS_TRACE_FLAGS_MASK": 255, + } +) + +func (x LogRecordFlags) Enum() *LogRecordFlags { + p := new(LogRecordFlags) + *p = x + return p +} + +func (x LogRecordFlags) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (LogRecordFlags) Descriptor() protoreflect.EnumDescriptor { + return file_opentelemetry_proto_logs_v1_logs_proto_enumTypes[1].Descriptor() +} + +func (LogRecordFlags) Type() protoreflect.EnumType { + return &file_opentelemetry_proto_logs_v1_logs_proto_enumTypes[1] +} + +func (x LogRecordFlags) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use LogRecordFlags.Descriptor instead. +func (LogRecordFlags) EnumDescriptor() ([]byte, []int) { + return file_opentelemetry_proto_logs_v1_logs_proto_rawDescGZIP(), []int{1} +} + +// LogsData represents the logs data that can be stored in a persistent storage, +// OR can be embedded by other protocols that transfer OTLP logs data but do not +// implement the OTLP protocol. +// +// The main difference between this message and collector protocol is that +// in this message there will not be any "control" or "metadata" specific to +// OTLP protocol. +// +// When new fields are added into this message, the OTLP request MUST be updated +// as well. +type LogsData struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // An array of ResourceLogs. + // For data coming from a single resource this array will typically contain + // one element. Intermediary nodes that receive data from multiple origins + // typically batch the data before forwarding further and in that case this + // array will contain multiple elements. + ResourceLogs []*ResourceLogs `protobuf:"bytes,1,rep,name=resource_logs,json=resourceLogs,proto3" json:"resource_logs,omitempty"` +} + +func (x *LogsData) Reset() { + *x = LogsData{} + if protoimpl.UnsafeEnabled { + mi := &file_opentelemetry_proto_logs_v1_logs_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LogsData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LogsData) ProtoMessage() {} + +func (x *LogsData) ProtoReflect() protoreflect.Message { + mi := &file_opentelemetry_proto_logs_v1_logs_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LogsData.ProtoReflect.Descriptor instead. +func (*LogsData) Descriptor() ([]byte, []int) { + return file_opentelemetry_proto_logs_v1_logs_proto_rawDescGZIP(), []int{0} +} + +func (x *LogsData) GetResourceLogs() []*ResourceLogs { + if x != nil { + return x.ResourceLogs + } + return nil +} + +// A collection of ScopeLogs from a Resource. +type ResourceLogs struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The resource for the logs in this message. + // If this field is not set then resource info is unknown. + Resource *v1.Resource `protobuf:"bytes,1,opt,name=resource,proto3" json:"resource,omitempty"` + // A list of ScopeLogs that originate from a resource. + ScopeLogs []*ScopeLogs `protobuf:"bytes,2,rep,name=scope_logs,json=scopeLogs,proto3" json:"scope_logs,omitempty"` + // The Schema URL, if known. This is the identifier of the Schema that the resource data + // is recorded in. Notably, the last part of the URL path is the version number of the + // schema: http[s]://server[:port]/path/. To learn more about Schema URL see + // https://opentelemetry.io/docs/specs/otel/schemas/#schema-url + // This schema_url applies to the data in the "resource" field. It does not apply + // to the data in the "scope_logs" field which have their own schema_url field. + SchemaUrl string `protobuf:"bytes,3,opt,name=schema_url,json=schemaUrl,proto3" json:"schema_url,omitempty"` +} + +func (x *ResourceLogs) Reset() { + *x = ResourceLogs{} + if protoimpl.UnsafeEnabled { + mi := &file_opentelemetry_proto_logs_v1_logs_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ResourceLogs) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResourceLogs) ProtoMessage() {} + +func (x *ResourceLogs) ProtoReflect() protoreflect.Message { + mi := &file_opentelemetry_proto_logs_v1_logs_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResourceLogs.ProtoReflect.Descriptor instead. +func (*ResourceLogs) Descriptor() ([]byte, []int) { + return file_opentelemetry_proto_logs_v1_logs_proto_rawDescGZIP(), []int{1} +} + +func (x *ResourceLogs) GetResource() *v1.Resource { + if x != nil { + return x.Resource + } + return nil +} + +func (x *ResourceLogs) GetScopeLogs() []*ScopeLogs { + if x != nil { + return x.ScopeLogs + } + return nil +} + +func (x *ResourceLogs) GetSchemaUrl() string { + if x != nil { + return x.SchemaUrl + } + return "" +} + +// A collection of Logs produced by a Scope. +type ScopeLogs struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The instrumentation scope information for the logs in this message. + // Semantically when InstrumentationScope isn't set, it is equivalent with + // an empty instrumentation scope name (unknown). + Scope *v11.InstrumentationScope `protobuf:"bytes,1,opt,name=scope,proto3" json:"scope,omitempty"` + // A list of log records. + LogRecords []*LogRecord `protobuf:"bytes,2,rep,name=log_records,json=logRecords,proto3" json:"log_records,omitempty"` + // The Schema URL, if known. This is the identifier of the Schema that the log data + // is recorded in. Notably, the last part of the URL path is the version number of the + // schema: http[s]://server[:port]/path/. To learn more about Schema URL see + // https://opentelemetry.io/docs/specs/otel/schemas/#schema-url + // This schema_url applies to the data in the "scope" field and all logs in the + // "log_records" field. + SchemaUrl string `protobuf:"bytes,3,opt,name=schema_url,json=schemaUrl,proto3" json:"schema_url,omitempty"` +} + +func (x *ScopeLogs) Reset() { + *x = ScopeLogs{} + if protoimpl.UnsafeEnabled { + mi := &file_opentelemetry_proto_logs_v1_logs_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ScopeLogs) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ScopeLogs) ProtoMessage() {} + +func (x *ScopeLogs) ProtoReflect() protoreflect.Message { + mi := &file_opentelemetry_proto_logs_v1_logs_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ScopeLogs.ProtoReflect.Descriptor instead. +func (*ScopeLogs) Descriptor() ([]byte, []int) { + return file_opentelemetry_proto_logs_v1_logs_proto_rawDescGZIP(), []int{2} +} + +func (x *ScopeLogs) GetScope() *v11.InstrumentationScope { + if x != nil { + return x.Scope + } + return nil +} + +func (x *ScopeLogs) GetLogRecords() []*LogRecord { + if x != nil { + return x.LogRecords + } + return nil +} + +func (x *ScopeLogs) GetSchemaUrl() string { + if x != nil { + return x.SchemaUrl + } + return "" +} + +// A log record according to OpenTelemetry Log Data Model: +// https://github.com/open-telemetry/oteps/blob/main/text/logs/0097-log-data-model.md +type LogRecord struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // time_unix_nano is the time when the event occurred. + // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. + // Value of 0 indicates unknown or missing timestamp. + TimeUnixNano uint64 `protobuf:"fixed64,1,opt,name=time_unix_nano,json=timeUnixNano,proto3" json:"time_unix_nano,omitempty"` + // Time when the event was observed by the collection system. + // For events that originate in OpenTelemetry (e.g. using OpenTelemetry Logging SDK) + // this timestamp is typically set at the generation time and is equal to Timestamp. + // For events originating externally and collected by OpenTelemetry (e.g. using + // Collector) this is the time when OpenTelemetry's code observed the event measured + // by the clock of the OpenTelemetry code. This field MUST be set once the event is + // observed by OpenTelemetry. + // + // For converting OpenTelemetry log data to formats that support only one timestamp or + // when receiving OpenTelemetry log data by recipients that support only one timestamp + // internally the following logic is recommended: + // - Use time_unix_nano if it is present, otherwise use observed_time_unix_nano. + // + // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. + // Value of 0 indicates unknown or missing timestamp. + ObservedTimeUnixNano uint64 `protobuf:"fixed64,11,opt,name=observed_time_unix_nano,json=observedTimeUnixNano,proto3" json:"observed_time_unix_nano,omitempty"` + // Numerical value of the severity, normalized to values described in Log Data Model. + // [Optional]. + SeverityNumber SeverityNumber `protobuf:"varint,2,opt,name=severity_number,json=severityNumber,proto3,enum=opentelemetry.proto.logs.v1.SeverityNumber" json:"severity_number,omitempty"` + // The severity text (also known as log level). The original string representation as + // it is known at the source. [Optional]. + SeverityText string `protobuf:"bytes,3,opt,name=severity_text,json=severityText,proto3" json:"severity_text,omitempty"` + // A value containing the body of the log record. Can be for example a human-readable + // string message (including multi-line) describing the event in a free form or it can + // be a structured data composed of arrays and maps of other values. [Optional]. + Body *v11.AnyValue `protobuf:"bytes,5,opt,name=body,proto3" json:"body,omitempty"` + // Additional attributes that describe the specific event occurrence. [Optional]. + // Attribute keys MUST be unique (it is not allowed to have more than one + // attribute with the same key). + // The behavior of software that receives duplicated keys can be unpredictable. + Attributes []*v11.KeyValue `protobuf:"bytes,6,rep,name=attributes,proto3" json:"attributes,omitempty"` + DroppedAttributesCount uint32 `protobuf:"varint,7,opt,name=dropped_attributes_count,json=droppedAttributesCount,proto3" json:"dropped_attributes_count,omitempty"` + // Flags, a bit field. 8 least significant bits are the trace flags as + // defined in W3C Trace Context specification. 24 most significant bits are reserved + // and must be set to 0. Readers must not assume that 24 most significant bits + // will be zero and must correctly mask the bits when reading 8-bit trace flag (use + // flags & LOG_RECORD_FLAGS_TRACE_FLAGS_MASK). [Optional]. + Flags uint32 `protobuf:"fixed32,8,opt,name=flags,proto3" json:"flags,omitempty"` + // A unique identifier for a trace. All logs from the same trace share + // the same `trace_id`. The ID is a 16-byte array. An ID with all zeroes OR + // of length other than 16 bytes is considered invalid (empty string in OTLP/JSON + // is zero-length and thus is also invalid). + // + // This field is optional. + // + // The receivers SHOULD assume that the log record is not associated with a + // trace if any of the following is true: + // - the field is not present, + // - the field contains an invalid value. + TraceId []byte `protobuf:"bytes,9,opt,name=trace_id,json=traceId,proto3" json:"trace_id,omitempty"` + // A unique identifier for a span within a trace, assigned when the span + // is created. The ID is an 8-byte array. An ID with all zeroes OR of length + // other than 8 bytes is considered invalid (empty string in OTLP/JSON + // is zero-length and thus is also invalid). + // + // This field is optional. If the sender specifies a valid span_id then it SHOULD also + // specify a valid trace_id. + // + // The receivers SHOULD assume that the log record is not associated with a + // span if any of the following is true: + // - the field is not present, + // - the field contains an invalid value. + SpanId []byte `protobuf:"bytes,10,opt,name=span_id,json=spanId,proto3" json:"span_id,omitempty"` + // A unique identifier of event category/type. + // All events with the same event_name are expected to conform to the same + // schema for both their attributes and their body. + // + // Recommended to be fully qualified and short (no longer than 256 characters). + // + // Presence of event_name on the log record identifies this record + // as an event. + // + // [Optional]. + EventName string `protobuf:"bytes,12,opt,name=event_name,json=eventName,proto3" json:"event_name,omitempty"` +} + +func (x *LogRecord) Reset() { + *x = LogRecord{} + if protoimpl.UnsafeEnabled { + mi := &file_opentelemetry_proto_logs_v1_logs_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LogRecord) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LogRecord) ProtoMessage() {} + +func (x *LogRecord) ProtoReflect() protoreflect.Message { + mi := &file_opentelemetry_proto_logs_v1_logs_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LogRecord.ProtoReflect.Descriptor instead. +func (*LogRecord) Descriptor() ([]byte, []int) { + return file_opentelemetry_proto_logs_v1_logs_proto_rawDescGZIP(), []int{3} +} + +func (x *LogRecord) GetTimeUnixNano() uint64 { + if x != nil { + return x.TimeUnixNano + } + return 0 +} + +func (x *LogRecord) GetObservedTimeUnixNano() uint64 { + if x != nil { + return x.ObservedTimeUnixNano + } + return 0 +} + +func (x *LogRecord) GetSeverityNumber() SeverityNumber { + if x != nil { + return x.SeverityNumber + } + return SeverityNumber_SEVERITY_NUMBER_UNSPECIFIED +} + +func (x *LogRecord) GetSeverityText() string { + if x != nil { + return x.SeverityText + } + return "" +} + +func (x *LogRecord) GetBody() *v11.AnyValue { + if x != nil { + return x.Body + } + return nil +} + +func (x *LogRecord) GetAttributes() []*v11.KeyValue { + if x != nil { + return x.Attributes + } + return nil +} + +func (x *LogRecord) GetDroppedAttributesCount() uint32 { + if x != nil { + return x.DroppedAttributesCount + } + return 0 +} + +func (x *LogRecord) GetFlags() uint32 { + if x != nil { + return x.Flags + } + return 0 +} + +func (x *LogRecord) GetTraceId() []byte { + if x != nil { + return x.TraceId + } + return nil +} + +func (x *LogRecord) GetSpanId() []byte { + if x != nil { + return x.SpanId + } + return nil +} + +func (x *LogRecord) GetEventName() string { + if x != nil { + return x.EventName + } + return "" +} + +var File_opentelemetry_proto_logs_v1_logs_proto protoreflect.FileDescriptor + +var file_opentelemetry_proto_logs_v1_logs_proto_rawDesc = []byte{ + 0x0a, 0x26, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x6c, 0x6f, + 0x67, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1b, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, + 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x6c, 0x6f, + 0x67, 0x73, 0x2e, 0x76, 0x31, 0x1a, 0x2a, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, + 0x65, 0x74, 0x72, 0x79, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x1a, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2f, + 0x76, 0x31, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x22, 0x5a, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x73, 0x44, 0x61, 0x74, 0x61, 0x12, 0x4e, 0x0a, + 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, + 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x6c, 0x6f, 0x67, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, + 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x22, 0xc3, 0x01, + 0x0a, 0x0c, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x45, + 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x29, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x0a, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x6c, + 0x6f, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6f, 0x70, 0x65, 0x6e, + 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x4c, 0x6f, 0x67, + 0x73, 0x52, 0x09, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x1d, 0x0a, 0x0a, + 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x55, 0x72, 0x6c, 0x4a, 0x06, 0x08, 0xe8, 0x07, + 0x10, 0xe9, 0x07, 0x22, 0xbe, 0x01, 0x0a, 0x09, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x4c, 0x6f, 0x67, + 0x73, 0x12, 0x49, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x33, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, + 0x2e, 0x49, 0x6e, 0x73, 0x74, 0x72, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x53, 0x63, 0x6f, 0x70, 0x65, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x47, 0x0a, 0x0b, + 0x6c, 0x6f, 0x67, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x26, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, + 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, + 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x52, 0x0a, 0x6c, 0x6f, 0x67, 0x52, 0x65, + 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x5f, + 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x63, 0x68, 0x65, 0x6d, + 0x61, 0x55, 0x72, 0x6c, 0x22, 0x92, 0x04, 0x0a, 0x09, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x63, 0x6f, + 0x72, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x78, 0x5f, + 0x6e, 0x61, 0x6e, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x06, 0x52, 0x0c, 0x74, 0x69, 0x6d, 0x65, + 0x55, 0x6e, 0x69, 0x78, 0x4e, 0x61, 0x6e, 0x6f, 0x12, 0x35, 0x0a, 0x17, 0x6f, 0x62, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x6e, + 0x61, 0x6e, 0x6f, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x06, 0x52, 0x14, 0x6f, 0x62, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x55, 0x6e, 0x69, 0x78, 0x4e, 0x61, 0x6e, 0x6f, 0x12, + 0x54, 0x0a, 0x0f, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x6e, 0x75, 0x6d, 0x62, + 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, + 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x6c, + 0x6f, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x4e, + 0x75, 0x6d, 0x62, 0x65, 0x72, 0x52, 0x0e, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x4e, + 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, + 0x79, 0x5f, 0x74, 0x65, 0x78, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x65, + 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x54, 0x65, 0x78, 0x74, 0x12, 0x3b, 0x0a, 0x04, 0x62, 0x6f, + 0x64, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, + 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, + 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x6e, 0x79, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x47, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, + 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x70, + 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x65, 0x79, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, + 0x12, 0x38, 0x0a, 0x18, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x74, 0x72, + 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x16, 0x64, 0x72, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69, + 0x62, 0x75, 0x74, 0x65, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6c, + 0x61, 0x67, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x07, 0x52, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, + 0x12, 0x19, 0x0a, 0x08, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x07, 0x74, 0x72, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x73, + 0x70, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x70, + 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x4e, + 0x61, 0x6d, 0x65, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x2a, 0xc3, 0x05, 0x0a, 0x0e, 0x53, 0x65, + 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x1b, + 0x53, 0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x5f, + 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x19, 0x0a, + 0x15, 0x53, 0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, + 0x5f, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x45, 0x56, 0x45, + 0x52, 0x49, 0x54, 0x59, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x5f, 0x54, 0x52, 0x41, 0x43, + 0x45, 0x32, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, + 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x5f, 0x54, 0x52, 0x41, 0x43, 0x45, 0x33, 0x10, 0x03, + 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x4e, 0x55, 0x4d, + 0x42, 0x45, 0x52, 0x5f, 0x54, 0x52, 0x41, 0x43, 0x45, 0x34, 0x10, 0x04, 0x12, 0x19, 0x0a, 0x15, + 0x53, 0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x5f, + 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x05, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x45, 0x56, 0x45, 0x52, + 0x49, 0x54, 0x59, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x5f, 0x44, 0x45, 0x42, 0x55, 0x47, + 0x32, 0x10, 0x06, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, + 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x5f, 0x44, 0x45, 0x42, 0x55, 0x47, 0x33, 0x10, 0x07, 0x12, + 0x1a, 0x0a, 0x16, 0x53, 0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x4e, 0x55, 0x4d, 0x42, + 0x45, 0x52, 0x5f, 0x44, 0x45, 0x42, 0x55, 0x47, 0x34, 0x10, 0x08, 0x12, 0x18, 0x0a, 0x14, 0x53, + 0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x5f, 0x49, + 0x4e, 0x46, 0x4f, 0x10, 0x09, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x45, 0x56, 0x45, 0x52, 0x49, 0x54, + 0x59, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x46, 0x4f, 0x32, 0x10, 0x0a, + 0x12, 0x19, 0x0a, 0x15, 0x53, 0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x4e, 0x55, 0x4d, + 0x42, 0x45, 0x52, 0x5f, 0x49, 0x4e, 0x46, 0x4f, 0x33, 0x10, 0x0b, 0x12, 0x19, 0x0a, 0x15, 0x53, + 0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x5f, 0x49, + 0x4e, 0x46, 0x4f, 0x34, 0x10, 0x0c, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x45, 0x56, 0x45, 0x52, 0x49, + 0x54, 0x59, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x5f, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x0d, + 0x12, 0x19, 0x0a, 0x15, 0x53, 0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x4e, 0x55, 0x4d, + 0x42, 0x45, 0x52, 0x5f, 0x57, 0x41, 0x52, 0x4e, 0x32, 0x10, 0x0e, 0x12, 0x19, 0x0a, 0x15, 0x53, + 0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x5f, 0x57, + 0x41, 0x52, 0x4e, 0x33, 0x10, 0x0f, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x45, 0x56, 0x45, 0x52, 0x49, + 0x54, 0x59, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x5f, 0x57, 0x41, 0x52, 0x4e, 0x34, 0x10, + 0x10, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x4e, 0x55, + 0x4d, 0x42, 0x45, 0x52, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x11, 0x12, 0x1a, 0x0a, 0x16, + 0x53, 0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x5f, + 0x45, 0x52, 0x52, 0x4f, 0x52, 0x32, 0x10, 0x12, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x45, 0x56, 0x45, + 0x52, 0x49, 0x54, 0x59, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x5f, 0x45, 0x52, 0x52, 0x4f, + 0x52, 0x33, 0x10, 0x13, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, + 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x34, 0x10, 0x14, + 0x12, 0x19, 0x0a, 0x15, 0x53, 0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x4e, 0x55, 0x4d, + 0x42, 0x45, 0x52, 0x5f, 0x46, 0x41, 0x54, 0x41, 0x4c, 0x10, 0x15, 0x12, 0x1a, 0x0a, 0x16, 0x53, + 0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x5f, 0x46, + 0x41, 0x54, 0x41, 0x4c, 0x32, 0x10, 0x16, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x45, 0x56, 0x45, 0x52, + 0x49, 0x54, 0x59, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x5f, 0x46, 0x41, 0x54, 0x41, 0x4c, + 0x33, 0x10, 0x17, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x45, 0x56, 0x45, 0x52, 0x49, 0x54, 0x59, 0x5f, + 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x5f, 0x46, 0x41, 0x54, 0x41, 0x4c, 0x34, 0x10, 0x18, 0x2a, + 0x59, 0x0a, 0x0e, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x46, 0x6c, 0x61, 0x67, + 0x73, 0x12, 0x1f, 0x0a, 0x1b, 0x4c, 0x4f, 0x47, 0x5f, 0x52, 0x45, 0x43, 0x4f, 0x52, 0x44, 0x5f, + 0x46, 0x4c, 0x41, 0x47, 0x53, 0x5f, 0x44, 0x4f, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x55, 0x53, 0x45, + 0x10, 0x00, 0x12, 0x26, 0x0a, 0x21, 0x4c, 0x4f, 0x47, 0x5f, 0x52, 0x45, 0x43, 0x4f, 0x52, 0x44, + 0x5f, 0x46, 0x4c, 0x41, 0x47, 0x53, 0x5f, 0x54, 0x52, 0x41, 0x43, 0x45, 0x5f, 0x46, 0x4c, 0x41, + 0x47, 0x53, 0x5f, 0x4d, 0x41, 0x53, 0x4b, 0x10, 0xff, 0x01, 0x42, 0x73, 0x0a, 0x1e, 0x69, 0x6f, + 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x76, 0x31, 0x42, 0x09, 0x4c, 0x6f, + 0x67, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x26, 0x67, 0x6f, 0x2e, 0x6f, 0x70, + 0x65, 0x6e, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x2e, 0x69, 0x6f, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6f, 0x74, 0x6c, 0x70, 0x2f, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x76, + 0x31, 0xaa, 0x02, 0x1b, 0x4f, 0x70, 0x65, 0x6e, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, + 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, 0x6f, 0x67, 0x73, 0x2e, 0x56, 0x31, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_opentelemetry_proto_logs_v1_logs_proto_rawDescOnce sync.Once + file_opentelemetry_proto_logs_v1_logs_proto_rawDescData = file_opentelemetry_proto_logs_v1_logs_proto_rawDesc +) + +func file_opentelemetry_proto_logs_v1_logs_proto_rawDescGZIP() []byte { + file_opentelemetry_proto_logs_v1_logs_proto_rawDescOnce.Do(func() { + file_opentelemetry_proto_logs_v1_logs_proto_rawDescData = protoimpl.X.CompressGZIP(file_opentelemetry_proto_logs_v1_logs_proto_rawDescData) + }) + return file_opentelemetry_proto_logs_v1_logs_proto_rawDescData +} + +var file_opentelemetry_proto_logs_v1_logs_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_opentelemetry_proto_logs_v1_logs_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_opentelemetry_proto_logs_v1_logs_proto_goTypes = []interface{}{ + (SeverityNumber)(0), // 0: opentelemetry.proto.logs.v1.SeverityNumber + (LogRecordFlags)(0), // 1: opentelemetry.proto.logs.v1.LogRecordFlags + (*LogsData)(nil), // 2: opentelemetry.proto.logs.v1.LogsData + (*ResourceLogs)(nil), // 3: opentelemetry.proto.logs.v1.ResourceLogs + (*ScopeLogs)(nil), // 4: opentelemetry.proto.logs.v1.ScopeLogs + (*LogRecord)(nil), // 5: opentelemetry.proto.logs.v1.LogRecord + (*v1.Resource)(nil), // 6: opentelemetry.proto.resource.v1.Resource + (*v11.InstrumentationScope)(nil), // 7: opentelemetry.proto.common.v1.InstrumentationScope + (*v11.AnyValue)(nil), // 8: opentelemetry.proto.common.v1.AnyValue + (*v11.KeyValue)(nil), // 9: opentelemetry.proto.common.v1.KeyValue +} +var file_opentelemetry_proto_logs_v1_logs_proto_depIdxs = []int32{ + 3, // 0: opentelemetry.proto.logs.v1.LogsData.resource_logs:type_name -> opentelemetry.proto.logs.v1.ResourceLogs + 6, // 1: opentelemetry.proto.logs.v1.ResourceLogs.resource:type_name -> opentelemetry.proto.resource.v1.Resource + 4, // 2: opentelemetry.proto.logs.v1.ResourceLogs.scope_logs:type_name -> opentelemetry.proto.logs.v1.ScopeLogs + 7, // 3: opentelemetry.proto.logs.v1.ScopeLogs.scope:type_name -> opentelemetry.proto.common.v1.InstrumentationScope + 5, // 4: opentelemetry.proto.logs.v1.ScopeLogs.log_records:type_name -> opentelemetry.proto.logs.v1.LogRecord + 0, // 5: opentelemetry.proto.logs.v1.LogRecord.severity_number:type_name -> opentelemetry.proto.logs.v1.SeverityNumber + 8, // 6: opentelemetry.proto.logs.v1.LogRecord.body:type_name -> opentelemetry.proto.common.v1.AnyValue + 9, // 7: opentelemetry.proto.logs.v1.LogRecord.attributes:type_name -> opentelemetry.proto.common.v1.KeyValue + 8, // [8:8] is the sub-list for method output_type + 8, // [8:8] is the sub-list for method input_type + 8, // [8:8] is the sub-list for extension type_name + 8, // [8:8] is the sub-list for extension extendee + 0, // [0:8] is the sub-list for field type_name +} + +func init() { file_opentelemetry_proto_logs_v1_logs_proto_init() } +func file_opentelemetry_proto_logs_v1_logs_proto_init() { + if File_opentelemetry_proto_logs_v1_logs_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_opentelemetry_proto_logs_v1_logs_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LogsData); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_opentelemetry_proto_logs_v1_logs_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ResourceLogs); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_opentelemetry_proto_logs_v1_logs_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ScopeLogs); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_opentelemetry_proto_logs_v1_logs_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LogRecord); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_opentelemetry_proto_logs_v1_logs_proto_rawDesc, + NumEnums: 2, + NumMessages: 4, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_opentelemetry_proto_logs_v1_logs_proto_goTypes, + DependencyIndexes: file_opentelemetry_proto_logs_v1_logs_proto_depIdxs, + EnumInfos: file_opentelemetry_proto_logs_v1_logs_proto_enumTypes, + MessageInfos: file_opentelemetry_proto_logs_v1_logs_proto_msgTypes, + }.Build() + File_opentelemetry_proto_logs_v1_logs_proto = out.File + file_opentelemetry_proto_logs_v1_logs_proto_rawDesc = nil + file_opentelemetry_proto_logs_v1_logs_proto_goTypes = nil + file_opentelemetry_proto_logs_v1_logs_proto_depIdxs = nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index b50ea91b42..d3c0e43298 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -948,6 +948,14 @@ go.opentelemetry.io/otel/semconv/v1.40.0/rpcconv go.opentelemetry.io/otel/semconv/v1.41.0 go.opentelemetry.io/otel/semconv/v1.41.0/httpconv go.opentelemetry.io/otel/semconv/v1.41.0/otelconv +# go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.20.0 +## explicit; go 1.25.0 +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/observ +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/retry +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/transform +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc/internal/x # go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 ## explicit; go 1.25.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc @@ -977,6 +985,13 @@ go.opentelemetry.io/otel/exporters/prometheus/internal go.opentelemetry.io/otel/exporters/prometheus/internal/counter go.opentelemetry.io/otel/exporters/prometheus/internal/observ go.opentelemetry.io/otel/exporters/prometheus/internal/x +# go.opentelemetry.io/otel/log v0.20.0 +## explicit; go 1.25.0 +go.opentelemetry.io/otel/log +go.opentelemetry.io/otel/log/embedded +go.opentelemetry.io/otel/log/global +go.opentelemetry.io/otel/log/internal/global +go.opentelemetry.io/otel/log/noop # go.opentelemetry.io/otel/metric v1.44.0 ## explicit; go 1.25.0 go.opentelemetry.io/otel/metric @@ -992,6 +1007,11 @@ go.opentelemetry.io/otel/sdk/trace go.opentelemetry.io/otel/sdk/trace/internal/env go.opentelemetry.io/otel/sdk/trace/internal/observ go.opentelemetry.io/otel/sdk/trace/tracetest +# go.opentelemetry.io/otel/sdk/log v0.20.0 +## explicit; go 1.25.0 +go.opentelemetry.io/otel/sdk/log +go.opentelemetry.io/otel/sdk/log/internal/observ +go.opentelemetry.io/otel/sdk/log/internal/x # go.opentelemetry.io/otel/sdk/metric v1.44.0 ## explicit; go 1.25.0 go.opentelemetry.io/otel/sdk/metric @@ -1010,9 +1030,11 @@ go.opentelemetry.io/otel/trace/internal/telemetry go.opentelemetry.io/otel/trace/noop # go.opentelemetry.io/proto/otlp v1.11.0 ## explicit; go 1.25.0 +go.opentelemetry.io/proto/otlp/collector/logs/v1 go.opentelemetry.io/proto/otlp/collector/metrics/v1 go.opentelemetry.io/proto/otlp/collector/trace/v1 go.opentelemetry.io/proto/otlp/common/v1 +go.opentelemetry.io/proto/otlp/logs/v1 go.opentelemetry.io/proto/otlp/metrics/v1 go.opentelemetry.io/proto/otlp/resource/v1 go.opentelemetry.io/proto/otlp/trace/v1 From c06911fcb4e4189821731237db64fb16da2fbd43 Mon Sep 17 00:00:00 2001 From: krisztianfekete Date: Tue, 15 Sep 2026 12:42:13 +0200 Subject: [PATCH 2/3] (otel): export actor state records over otlp --- AGENTS.md | 6 +- cmd/ateapi/internal/controlapi/crash.go | 4 +- cmd/ateapi/internal/controlapi/crash_test.go | 80 ++++++ cmd/ateapi/internal/controlapi/workflow.go | 4 +- .../internal/controlapi/workflow_test.go | 17 +- cmd/ateapi/main.go | 12 + docs/dev/best-practices/otel-collector.md | 42 ++-- docs/metrics/registry/events.yaml | 102 ++++++++ docs/metrics/registry/manifest.yaml | 2 +- docs/metrics/registry/metrics.yaml | 86 +++++++ docs/metrics/substrate.yaml | 13 + docs/observability.md | 26 +- internal/actorevent/actorevent.go | 143 +++++++++++ internal/actorevent/actorevent_test.go | 235 ++++++++++++++++++ internal/e2e/collector_metrics.go | 47 ++++ internal/e2e/collector_metrics_test.go | 104 ++++++++ internal/e2e/suites/metrics/metrics_test.go | 35 ++- internal/serverboot/logging.go | 135 ++++++++++ internal/serverboot/logging_test.go | 169 +++++++++++++ .../ate-install/kind/ate-otel-config.yaml | 5 + .../ate-install/kind/otel-collector.yaml | 33 ++- 21 files changed, 1266 insertions(+), 34 deletions(-) create mode 100644 docs/metrics/registry/events.yaml create mode 100644 internal/actorevent/actorevent.go create mode 100644 internal/actorevent/actorevent_test.go create mode 100644 internal/serverboot/logging.go create mode 100644 internal/serverboot/logging_test.go diff --git a/AGENTS.md b/AGENTS.md index e7dd4263a8..7a7a0b096a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -67,9 +67,11 @@ Agent Substrate uses a `Makefile` for its build and test tasks. ## Metrics -`docs/metrics/registry/metrics.yaml` is an [OpenTelemetry Weaver](https://github.com/open-telemetry/weaver) registry. It defines every metric instrument the ate system components emit, and the permitted values of each label. Read it to find an instrument or its labels. +`docs/metrics/registry/` is an [OpenTelemetry Weaver](https://github.com/open-telemetry/weaver) registry. `metrics.yaml` defines every metric instrument the ate system components emit, plus every attribute any signal uses and the permitted values of each. `events.yaml` defines the log events emitted over OTLP. Read them to find an instrument, an event, or their attributes. -If you add or rename an instrument, or add a metric label, follow `docs/dev/best-practices/metrics.md`, update the registry, and run `hack/verify/metrics.sh`. `make verify` runs the same check. +If you add or rename an instrument or an event, or add an attribute, follow `docs/dev/best-practices/metrics.md`, update the registry, and run `hack/verify/metrics.sh`. `make verify` runs the same check. Weaver reads the whole directory, so a new file in it is checked straight away. + +Some attributes are defined in the registry but barred from metric labels: actor identity is the main one. The `note` on each says so, and `cardinality_rules` in `docs/metrics/substrate.yaml` is where the bar lives. `docs/metrics/substrate.yaml` holds the rules Weaver cannot express: the cardinality rules, the known exceptions, and the subsystems that emit no metrics. Read `blind_spots` before you attribute a fault to a component. diff --git a/cmd/ateapi/internal/controlapi/crash.go b/cmd/ateapi/internal/controlapi/crash.go index 6a445b7df3..b50e083ecb 100644 --- a/cmd/ateapi/internal/controlapi/crash.go +++ b/cmd/ateapi/internal/controlapi/crash.go @@ -21,6 +21,7 @@ import ( "log/slog" "github.com/agent-substrate/substrate/cmd/ateapi/internal/store" + "github.com/agent-substrate/substrate/internal/actorevent" "github.com/agent-substrate/substrate/internal/ateattr" "github.com/agent-substrate/substrate/internal/ateerrors" "github.com/agent-substrate/substrate/internal/resources" @@ -125,7 +126,8 @@ func logActorCrashed(ctx context.Context, actor *ateapipb.Actor, opName, reason attrs = append(attrs, slog.String(string(ateattr.ActorOperationNameKey), opName)) attrs = append(attrs, slog.String(string(ateattr.ActorStateKey), ateattr.ActorStateCrashed)) attrs = append(attrs, ateattr.FailureLogAttrs(reason)...) - slog.LogAttrs(ctx, slog.LevelError, "Actor crashed", attrs...) + slog.LogAttrs(ctx, slog.LevelError, actorevent.CrashedBody, attrs...) + actorevent.Emit(ctx, actorevent.Crashed, attrs) } // crashActorStore encapsulates the subset of store operations needed to crash diff --git a/cmd/ateapi/internal/controlapi/crash_test.go b/cmd/ateapi/internal/controlapi/crash_test.go index 306647beda..b85ff80ce6 100644 --- a/cmd/ateapi/internal/controlapi/crash_test.go +++ b/cmd/ateapi/internal/controlapi/crash_test.go @@ -19,15 +19,21 @@ import ( "errors" "log/slog" "maps" + "slices" "strings" + "sync" "testing" "github.com/agent-substrate/substrate/cmd/ateapi/internal/store" "github.com/agent-substrate/substrate/cmd/ateapi/internal/store/storetest" + "github.com/agent-substrate/substrate/internal/actorevent" "github.com/agent-substrate/substrate/internal/ateattr" "github.com/agent-substrate/substrate/internal/ateerrors" "github.com/agent-substrate/substrate/internal/resources" "github.com/agent-substrate/substrate/pkg/proto/ateapipb" + otellog "go.opentelemetry.io/otel/log" + "go.opentelemetry.io/otel/log/global" + sdklog "go.opentelemetry.io/otel/sdk/log" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "google.golang.org/grpc/codes" @@ -604,6 +610,64 @@ func logRecords(t *testing.T, msg string) *[]map[string]string { return &records } +// otlpEvent is one captured record from the OTLP copy of a log record. +type otlpEvent struct { + name string + attrs map[string]string +} + +var ( + otlpSinkOnce sync.Once + otlpSinkMu sync.Mutex + otlpSink []otlpEvent +) + +type otlpSinkExporter struct{} + +func (otlpSinkExporter) Export(_ context.Context, records []sdklog.Record) error { + otlpSinkMu.Lock() + defer otlpSinkMu.Unlock() + for _, r := range records { + e := otlpEvent{name: r.EventName(), attrs: map[string]string{}} + r.WalkAttributes(func(kv otellog.KeyValue) bool { + e.attrs[kv.Key] = kv.Value.String() + return true + }) + otlpSink = append(otlpSink, e) + } + return nil +} + +func (otlpSinkExporter) Shutdown(context.Context) error { return nil } +func (otlpSinkExporter) ForceFlush(context.Context) error { return nil } + +// otlpEvents captures the events emitted while a test runs, so a test can assert +// the OTLP copy beside the stdout one. The global logger provider only ever +// delegates once, so one provider serves the whole binary and each call clears +// the sink. Like logRecords, this makes the caller non-parallel. +func otlpEvents(t *testing.T) func() []otlpEvent { + t.Helper() + + otlpSinkOnce.Do(func() { + global.SetLoggerProvider(sdklog.NewLoggerProvider( + sdklog.WithProcessor(sdklog.NewSimpleProcessor(otlpSinkExporter{})))) + }) + + clear := func() { + otlpSinkMu.Lock() + defer otlpSinkMu.Unlock() + otlpSink = nil + } + clear() + t.Cleanup(clear) + + return func() []otlpEvent { + otlpSinkMu.Lock() + defer otlpSinkMu.Unlock() + return slices.Clone(otlpSink) + } +} + type slogHandlerFunc func(slog.Record) func (f slogHandlerFunc) Enabled(context.Context, slog.Level) bool { return true } @@ -623,6 +687,7 @@ func TestCrashActor_RecordAndCounterAgree(t *testing.T) { t.Fatalf("RegisterActorCrashes: %v", err) } records := crashRecords(t) + events := otlpEvents(t) ctx := context.Background() st, cleanup := storetest.SetupTestStore(t) @@ -665,6 +730,18 @@ func TestCrashActor_RecordAndCounterAgree(t *testing.T) { t.Error("crash record carries no ate.actor.uid; it cannot survive a name reuse") } + // The OTLP copy is the same record under an event name. + gotEvents := events() + if len(gotEvents) != 1 { + t.Fatalf("got %d crash events, want 1: %v", len(gotEvents), gotEvents) + } + if gotEvents[0].name != actorevent.Crashed.Name { + t.Errorf("event name = %q, want %q", gotEvents[0].name, actorevent.Crashed.Name) + } + if !maps.Equal(gotEvents[0].attrs, got) { + t.Errorf("crash event attributes = %v, want the stdout record's %v", gotEvents[0].attrs, got) + } + // Re-crashing an already-crashed actor must move neither signal. if err := crashActor(ctx, st, actorRef, ateattr.OperationResume, ateattr.ReasonWorkerPodGone); err != nil { t.Fatalf("second crashActor: %v", err) @@ -672,4 +749,7 @@ func TestCrashActor_RecordAndCounterAgree(t *testing.T) { if len(*records) != 1 { t.Errorf("got %d crash records after re-crashing, want 1", len(*records)) } + if gotEvents := events(); len(gotEvents) != 1 { + t.Errorf("got %d crash events after re-crashing, want 1", len(gotEvents)) + } } diff --git a/cmd/ateapi/internal/controlapi/workflow.go b/cmd/ateapi/internal/controlapi/workflow.go index 1057169ba3..f9ab2faf3d 100644 --- a/cmd/ateapi/internal/controlapi/workflow.go +++ b/cmd/ateapi/internal/controlapi/workflow.go @@ -23,6 +23,7 @@ import ( "github.com/agent-substrate/substrate/cmd/ateapi/internal/scheduling" "github.com/agent-substrate/substrate/cmd/ateapi/internal/store" "github.com/agent-substrate/substrate/cmd/ateapi/internal/workercache" + "github.com/agent-substrate/substrate/internal/actorevent" "github.com/agent-substrate/substrate/internal/ateattr" "github.com/agent-substrate/substrate/internal/objectstore" "github.com/agent-substrate/substrate/internal/resources" @@ -95,7 +96,8 @@ func logActorState(ctx context.Context, actor *ateapipb.Actor, opName, state str attrs = append(attrs, slog.String(string(ateattr.ActorOperationNameKey), ateattr.NormalizeOperationName(opName)), slog.String(string(ateattr.ActorStateKey), state)) - slog.LogAttrs(ctx, slog.LevelInfo, "Actor state changed", attrs...) + slog.LogAttrs(ctx, slog.LevelInfo, actorevent.StateChangedBody, attrs...) + actorevent.Emit(ctx, actorevent.StateChanged, attrs) } // ActorWorkflow handles the workflows for actor's resume / suspend operations. diff --git a/cmd/ateapi/internal/controlapi/workflow_test.go b/cmd/ateapi/internal/controlapi/workflow_test.go index b986095385..7f038a6b52 100644 --- a/cmd/ateapi/internal/controlapi/workflow_test.go +++ b/cmd/ateapi/internal/controlapi/workflow_test.go @@ -16,10 +16,12 @@ package controlapi import ( "context" + "maps" "testing" "github.com/agent-substrate/substrate/cmd/ateapi/internal/store" "github.com/agent-substrate/substrate/cmd/ateapi/internal/store/storetest" + "github.com/agent-substrate/substrate/internal/actorevent" "github.com/agent-substrate/substrate/internal/ateattr" "github.com/agent-substrate/substrate/internal/resources" "github.com/agent-substrate/substrate/pkg/proto/ateapipb" @@ -84,7 +86,8 @@ func TestActorStateChangeRecords(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := context.Background() - records := logRecords(t, "Actor state changed") + records := logRecords(t, actorevent.StateChangedBody) + events := otlpEvents(t) persistence := newTestPersistence(t) storetest.MustCreateAtespace(t, ctx, persistence, tmplAtespace) @@ -134,6 +137,18 @@ func TestActorStateChangeRecords(t *testing.T) { t.Errorf("got %d attributes, want %d: %v", len(got), len(want), got) } + // The OTLP copy is the same record under an event name. + gotEvents := events() + if len(gotEvents) != 1 { + t.Fatalf("got %d state events, want 1: %v", len(gotEvents), gotEvents) + } + if gotEvents[0].name != actorevent.StateChanged.Name { + t.Errorf("event name = %q, want %q", gotEvents[0].name, actorevent.StateChanged.Name) + } + if !maps.Equal(gotEvents[0].attrs, got) { + t.Errorf("state event attributes = %v, want the stdout record's %v", gotEvents[0].attrs, got) + } + stored, err := persistence.GetActor(ctx, actorRef) if err != nil { t.Fatalf("reload actor: %v", err) diff --git a/cmd/ateapi/main.go b/cmd/ateapi/main.go index 47c7ccebf1..d07b97f697 100644 --- a/cmd/ateapi/main.go +++ b/cmd/ateapi/main.go @@ -121,6 +121,18 @@ func main() { } defer serverboot.ShutdownProvider("MeterProvider", mp.Shutdown) + lp, err := serverboot.InitLogging(ctx, serverboot.LoggingOptions{ + ServiceName: "ateapi", + Exporter: serverboot.ResolveLogsExporter(ctx, serverboot.LogsExporterNone), + }) + if err != nil { + serverboot.Fatal(ctx, "Failed to initialize logging", err) + } + // Nil when the exporter is none. + if lp != nil { + defer serverboot.ShutdownProvider("LoggerProvider", lp.Shutdown) + } + loadFlagsFromEnv() logFlagValues(ctx) authenticationConfig, err := ateapiauth.LoadAuthenticationConfig(*authenticationConfigFile) diff --git a/docs/dev/best-practices/otel-collector.md b/docs/dev/best-practices/otel-collector.md index 3b0801380f..996f4f1adb 100644 --- a/docs/dev/best-practices/otel-collector.md +++ b/docs/dev/best-practices/otel-collector.md @@ -253,8 +253,9 @@ data: processors: [memory_limiter, k8s_attributes, batch/metrics] exporters: [debug] # Logs arrive over the same OTLP receiver on 4317 -- one port serves - # all three signals. This pipeline is for workloads instrumented - # with the OTel logs SDK; see "A Note on Logs" below. + # all three signals. This pipeline carries ateapi's actor lifecycle + # events and any workload instrumented with the OTel logs SDK; see + # "A Note on Logs" below. Do not add a sampler or a filter to it. logs: receivers: [otlp] processors: [memory_limiter, k8s_attributes, batch/logs] @@ -432,13 +433,15 @@ for a worked example. ### A Note on Logs -The collector configs here include a logs pipeline, but **Substrate does not -currently export logs over OTLP.** `serverboot.InitLogger` writes structured -JSON to stdout, and `ateom` wraps actor container output with the `ate.*` -metadata labels described in -[Actor Observability](../../observability.md) — also on stdout. There is no -`LoggerProvider` or OTLP log exporter in `internal/serverboot`, alongside -`InitTracing` and `InitMetrics`. +**Substrate exports one thing over OTLP: the actor lifecycle events**, from +ateapi, through `serverboot.InitLogging`. They are off unless +`OTEL_LOGS_EXPORTER=otlp` is set, which only the kind overlay does today. See +[the same records over OTLP](../../observability.md#the-same-records-over-otlp). + +Everything else is stdout. `serverboot.InitLogger` writes structured JSON there, +and `ateom` wraps actor container output with the `ate.*` metadata labels +described in [Actor Observability](../../observability.md). No worker pod +exports a log record at all: the ateom relay carries traces and metrics only. Those labels sit in a nested group (`labels`, or `logging.googleapis.com/labels` on GKE, where the key promotes the group into `LogEntry.labels`). A filelog @@ -456,13 +459,17 @@ top-level and lowercase hex, so the filelog receiver's `trace_parser` maps them onto the log record's own trace fields with no transformation. Those logs are collected by whatever agent already reads container stdout on -your nodes — Cloud Logging's agent on GKE, or your own. The collector is not -in that path. +your nodes. The collector is not in that path. + +If you do add such an agent, note that ateapi writes the lifecycle records to +both stdout and OTLP. Reading its stdout as well gives you each record twice: +either exclude the `ate-system` namespace from the agent, or drop the records +whose instrumentation scope is +`github.com/agent-substrate/substrate/internal/actorevent`. -The logs pipeline is therefore there for **your** workloads: actors or -services instrumented with the OpenTelemetry logs SDK that push OTLP log -records to the same endpoint. It costs nothing to leave configured, and it -means an actor that adopts OTLP logging needs no collector change. +The logs pipeline also serves **your** workloads: actors or services +instrumented with the OpenTelemetry logs SDK that push OTLP log records to the +same endpoint. It needs no collector change beyond being configured. ## Constraints You Cannot Configure Around @@ -470,8 +477,9 @@ These are properties of Substrate's current exporter setup (`internal/serverboot/serverboot.go`), not of your collector. Tracked in [#563](https://github.com/agent-substrate/substrate/issues/563). -**TLS is not supported.** The exporters are constructed with -`otlptracegrpc.WithInsecure()`, which overrides scheme inference, so setting +**TLS is not supported.** All three exporters are constructed with +`WithInsecure()` — traces, metrics, and logs alike — which overrides scheme +inference, so setting `OTEL_EXPORTER_OTLP_ENDPOINT=https://…` does **not** produce a TLS connection — it silently stays plaintext. Keep the collector in-cluster and let it own the authenticated hop to your backend. diff --git a/docs/metrics/registry/events.yaml b/docs/metrics/registry/events.yaml new file mode 100644 index 0000000000..1ffc85954e --- /dev/null +++ b/docs/metrics/registry/events.yaml @@ -0,0 +1,102 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# The log events that Substrate emits over OTLP. Weaver reads this file with +# metrics.yaml, which holds every attribute the groups below refer to. +# +# An event name promises a fixed set of attributes. The list in each group is +# the reviewable form of the Keys field on the matching internal/actorevent +# Event, and a test in that package holds the two in step. +# +# Metrics cannot answer what state an actor is in: the cardinality rule +# no-actor-identity bars actor identity from metric labels. These events carry +# the identity, thus they are the only per-actor state answer. + +groups: + - id: event.ate.actor.state_changed + type: event + name: ate.actor.state_changed + stability: development + brief: An actor reached a new state. + note: > + ateapi writes one record for each committed transition, including the + first, when the actor is made, and the last, when the record is deleted. + The most recent record for an ate.actor.uid gives the state the actor is + in now, and its timestamp gives when that state began. + + A crash is the event ate.actor.crashed instead, because it carries the + failure keys and a higher severity. Both events set ate.actor.state, thus + a consumer that reads the state off the attribute sees every transition + and needs no map from a name to a state. + + Never sample or filter this stream. A consumer takes the last record for + an actor, thus one dropped record reports a stale state with no sign that + anything is missing. + annotations: + substrate: + emitted_by: [ateapi] + code_anchor: cmd/ateapi/internal/controlapi/workflow.go + cuj: What state is this actor in, and since when? + attributes: + - ref: ate.atespace + requirement_level: required + - ref: ate.actor.name + requirement_level: required + - ref: ate.actor.uid + requirement_level: required + - ref: ate.template.atespace + requirement_level: required + - ref: ate.template.name + requirement_level: required + - ref: ate.actor.operation.name + requirement_level: required + - ref: ate.actor.state + requirement_level: required + + - id: event.ate.actor.crashed + type: event + name: ate.actor.crashed + stability: development + brief: An actor was lost. + note: > + ateapi writes this beside the ate.actor.crashes counter, under the same + guard, thus the event and the counter cannot disagree. The counter gives + the rate; this event names which actor, which no metric label may. + + ate.actor.state is always crashed. It is set so a consumer reading the + state off one attribute reaches crashed without special-casing the name. + annotations: + substrate: + emitted_by: [ateapi] + code_anchor: cmd/ateapi/internal/controlapi/crash.go + cuj: Which actor was lost, and why? + attributes: + - ref: ate.atespace + requirement_level: required + - ref: ate.actor.name + requirement_level: required + - ref: ate.actor.uid + requirement_level: required + - ref: ate.template.atespace + requirement_level: required + - ref: ate.template.name + requirement_level: required + - ref: ate.actor.operation.name + requirement_level: required + - ref: ate.actor.state + requirement_level: required + - ref: ate.failure.reason + requirement_level: required + - ref: ate.failure.domain + requirement_level: required diff --git a/docs/metrics/registry/manifest.yaml b/docs/metrics/registry/manifest.yaml index 5b05fa00af..d499484b3f 100644 --- a/docs/metrics/registry/manifest.yaml +++ b/docs/metrics/registry/manifest.yaml @@ -24,5 +24,5 @@ # project publishes this registry later. name: substrate -description: The metric semantic conventions of Agent Substrate. +description: The metric and event semantic conventions of Agent Substrate. schema_url: https://ate.dev/schemas/substrate/1.0.0 diff --git a/docs/metrics/registry/metrics.yaml b/docs/metrics/registry/metrics.yaml index 45c6317310..e510dbca6e 100644 --- a/docs/metrics/registry/metrics.yaml +++ b/docs/metrics/registry/metrics.yaml @@ -31,10 +31,96 @@ groups: # Attribute groups # --------------------------------------------------------------------------- + - id: registry.ate.atespace + type: attribute_group + brief: > + The tenant and isolation boundary. It is shared by every resource type, + thus it stays at the top level and does not nest under one of them. + attributes: + - id: ate.atespace + stability: development + type: string + brief: The atespace of the subject of the signal. + note: > + Logs and spans only. The cardinality rule no-actor-identity in + docs/metrics/substrate.yaml forbids it as a metric label: the number + of values is not bounded at the target of 1 billion actors. + examples: [ate-demo-counter] + - id: registry.ate.actor type: attribute_group brief: The labels of an actor. attributes: + - id: ate.actor.name + stability: development + type: string + brief: The name of the actor, which addresses it within its atespace. + note: > + Logs and spans only, by the cardinality rule no-actor-identity in + docs/metrics/substrate.yaml. A name is free after a delete, thus a + query that spans a delete groups by ate.actor.uid and not by this. + examples: [counter-1] + - id: ate.actor.uid + stability: development + type: string + brief: The server-assigned unique key of the actor. + note: > + Logs and spans only, by the cardinality rule no-actor-identity in + docs/metrics/substrate.yaml. It separates two actors that held the + same name at different times. + examples: [8f2a1c4e6b0d47f1] + - id: ate.actor.state + stability: development + brief: The state the actor is in now. + note: > + Logs only. It is bounded, but it is only ever recorded beside actor + identity, which no metric may carry. The values mirror + ateapipb.ActorState with the ACTOR_STATE_ prefix dropped, so the log + vocabulary and the state machine cannot fork. + type: + members: + - id: resuming + stability: development + value: resuming + brief: ateapi is putting the actor on a worker. + - id: running + stability: development + value: running + brief: The actor runs on a worker. + - id: suspending + stability: development + value: suspending + brief: ateapi is writing the durable snapshot. + - id: suspended + stability: development + value: suspended + brief: The actor has a durable snapshot and holds no worker. + - id: pausing + stability: development + value: pausing + brief: ateapi is writing the node-local snapshot. + - id: paused + stability: development + value: paused + brief: The actor has a node-local snapshot. + - id: crashed + stability: development + value: crashed + brief: The actor is lost. ate.failure.reason says why. + - id: deleting + stability: development + value: deleting + brief: ateapi is removing the actor. + - id: deleted + stability: development + value: deleted + brief: > + The actor record is gone. It has no enum counterpart: no stored + state can stand for a record that no longer exists. + - id: unknown + stability: development + value: unknown + brief: The state is not in this list. This is the default value. - id: ate.actor.operation.name stability: development brief: The actor lifecycle operation that ateapi did. diff --git a/docs/metrics/substrate.yaml b/docs/metrics/substrate.yaml index 1f5920c4d6..8cc8d80bf7 100644 --- a/docs/metrics/substrate.yaml +++ b/docs/metrics/substrate.yaml @@ -218,6 +218,19 @@ lint_exceptions: This label is older than the naming convention of the router. A change to ate.router.park.outcome needs a change to the dashboards at the same time. atenet.router.route.duration had the same change before. + - attribute: none + metric: none + rule: One record goes to one place. + brief: > + ateapi writes each actor lifecycle record twice: to stdout, and as an OTLP + event. Nothing reads both today. No collector config in this repository + declares a filelog receiver, and neither enterprise collector is a + DaemonSet, thus no agent reads the stdout copy into the same pipeline. + The stdout copy is what kubectl logs shows. The OTLP copy is what a + backend queries and needs no knowledge of the stdout envelope. + An agent that reads pod stdout makes this a duplicate. Then drop one: + leave ate-system out of the globs of that agent, or drop the records with + the scope github.com/agent-substrate/substrate/internal/actorevent. # ----------------------------------------------------------------------------- # The subsystems with no metrics. An agent that reads only the metrics gives the diff --git a/docs/observability.md b/docs/observability.md index b7fe136f7a..e3ef4e4504 100644 --- a/docs/observability.md +++ b/docs/observability.md @@ -176,6 +176,25 @@ Creating an actor counts as a change. A new actor is born suspended, so it gets The counter carries the same reason but no actor identity, so this record is the only way to attribute a crash to one agent. The decision-point line that precedes it (`Setting Actor to crashed due to error`) carries only `ate.atespace` and `ate.actor.name`: it is written before the Actor is loaded, so no uid exists yet. +#### The same records over OTLP + +Both records also go out as OTLP log events, so a collector reads them without knowing substrate's stdout envelope. Set `OTEL_LOGS_EXPORTER=otlp` to turn it on; unset means `none`, which is what every environment but kind uses today. Only ateapi has a LoggerProvider — a worker pod cannot export a log record at all yet, because [the ateom relay](#the-ateom-otlp-relay) carries traces and metrics only. + +Two `event.name` values, which is the OTLP LogRecord's own field rather than an attribute: + +| `event.name` | Body | Severity | Attributes | +|---|---|---|---| +| `ate.actor.state_changed` | `Actor state changed` | 9 | the five identity keys, `ate.actor.operation.name`, `ate.actor.state` | +| `ate.actor.crashed` | `Actor crashed` | 17 | the above plus `ate.failure.reason`, `ate.failure.domain` | + +A crash is its own name because an event name promises a fixed set of attributes, and it carries two more. There is no name per state: `ate.actor.state` already says which transition happened, so a consumer still selects on that one attribute and needs no map from a name to a state. Both names are in [`docs/metrics/registry/events.yaml`](metrics/registry/events.yaml), which `make verify` checks. + +The attributes are the same flat `ate.*` keys as the stdout copy, so they arrive as real log attributes with no transform in front of them. Trace context is not among them: it goes on the record's own `TraceId` and `SpanId` fields, where the stdout copy's top-level `trace_id`/`span_id` would be mapped to anyway. The instrumentation scope is `github.com/agent-substrate/substrate/internal/actorevent`, which is how you select this stream, or exclude it. + +**Never sample or filter this stream.** A consumer takes the last event for an actor's uid, so one dropped record reports a stale state with no sign that anything is missing. This is the one stream where a sampling policy is a correctness bug rather than a cost trade. + +**Both copies exist on purpose.** No substrate or enterprise collector reads pod stdout today, so nothing is duplicated: the stdout copy is what `kubectl logs` shows and what keeps the component's bootstrap and crash output readable, and the OTLP copy is what a backend queries. If a `filelog` DaemonSet is ever added, drop one of the two — exclude ate-system from its include globs, or drop records whose scope is the one above. + ### Per-Actor Usage Events atelet emits one usage record per **executing** actor per sampling tick, from the same sweep that feeds the [`ate.actor.stats.*` metrics](#the-metric-registry). The two are the halves of one split: the metrics aggregate to the bounded template/pool label set a TSDB can hold, and everything carrying actor identity travels here, on the log stream, where cardinality is free. An idle fleet is silent by design — a worker with no executing actor emits nothing. @@ -276,9 +295,11 @@ The `ate.*` control-plane metric labels are either fixed value sets (operation, ### The metric registry -[`docs/metrics/registry/metrics.yaml`](metrics/registry/metrics.yaml) defines each instrument that the ate system components send, and the permitted values of each label. Use it as the only source of this data. +[`docs/metrics/registry/metrics.yaml`](metrics/registry/metrics.yaml) defines each instrument that the ate system components send, every attribute any signal uses, and the permitted values of each. [`events.yaml`](metrics/registry/events.yaml) beside it defines the [log events](#the-same-records-over-otlp). Use them as the only source of this data. + +A few attributes are defined there but barred from metric labels — actor identity above all. The `note` on each says so, and the cardinality rules below are where the bar lives. -It is an [OpenTelemetry Weaver](https://github.com/open-telemetry/weaver) registry. Weaver reads it, resolves each `ref`, and refuses a group or an attribute that is not correct: +It is an [OpenTelemetry Weaver](https://github.com/open-telemetry/weaver) registry. Weaver reads the whole directory, resolves each `ref`, and refuses a group or an attribute that is not correct: ```sh hack/verify/metrics.sh # the command that CI uses @@ -384,6 +405,7 @@ Telemetry is emitted the same way everywhere; only the backend differs between a | Path | service → in-cluster `opentelemetry-collector` | service → Google Managed Prometheus (GMP) | | Metrics | collector Prometheus exporter on `:8889` | Google Cloud Monitoring | | Traces | Jaeger UI | Google Cloud Trace | +| Logs | pod stdout; ateapi's [actor lifecycle events](#the-same-records-over-otlp) also to the collector's `debug` exporter | pod stdout. No OTLP logs: `OTEL_LOGS_EXPORTER` is unset | | Dashboards | Not supported | Google Cloud Monitoring (see [Dashboards](#5-dashboards)) | > In Kind, `ateapi`, `atelet`, `ate-controller`, and `atenet-router` are pointed at the in-cluster collector, and the controller propagates the endpoint to the ateom worker pods it creates, so all component telemetry lands locally. diff --git a/internal/actorevent/actorevent.go b/internal/actorevent/actorevent.go new file mode 100644 index 0000000000..7410060bbe --- /dev/null +++ b/internal/actorevent/actorevent.go @@ -0,0 +1,143 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package actorevent emits the actor lifecycle events over OTLP. Events go over +// OTLP; ordinary component logs stay on stdout. The caller passes the same +// []slog.Attr to both copies, so the two cannot drift. +// +// This is not an slog bridge. A bridge would put every component record on the +// wire, cannot set EventName, and would loop, because serverboot routes OTel SDK +// errors through slog. +// +// serverboot.InitLogging pairs this with a batching processor. These records sit +// on the actor resume path, so exporting inside Emit would put a blocking gRPC +// call there and make a slow collector look like control-plane latency. +package actorevent + +import ( + "context" + "log/slog" + "sync" + "time" + + "go.opentelemetry.io/otel/log" + "go.opentelemetry.io/otel/log/global" + + "github.com/agent-substrate/substrate/internal/ateattr" +) + +// ScopeName is how a consumer selects this stream. +const ScopeName = "github.com/agent-substrate/substrate/internal/actorevent" + +// Bodies match the stdout record's message, so both read alike. +const ( + StateChangedBody = "Actor state changed" + CrashedBody = "Actor crashed" +) + +// Event is one name in the closed vocabulary. Name is the LogRecord's own event +// name field, not an attribute. +// +// Keys is the attribute set the name promises. An event name means a fixed +// shape, so the tests hold the two in step and a caller cannot widen the record. +type Event struct { + Name string + Body string + Severity log.Severity + Keys []string +} + +// identityKeys is what ateattr.ActorLogAttrs writes, in its order. +var identityKeys = []string{ + string(ateattr.AtespaceKey), + string(ateattr.ActorNameKey), + string(ateattr.ActorUIDKey), + string(ateattr.TemplateAtespaceKey), + string(ateattr.TemplateNameKey), +} + +// Two names, because a crash has a different shape and severity. Only two, +// because ate.actor.state already says which transition happened. +var ( + StateChanged = Event{ + Name: "ate.actor.state_changed", + Body: StateChangedBody, + Severity: log.SeverityInfo, + Keys: append(append([]string{}, identityKeys...), + string(ateattr.ActorOperationNameKey), + string(ateattr.ActorStateKey)), + } + + Crashed = Event{ + Name: "ate.actor.crashed", + Body: CrashedBody, + Severity: log.SeverityError, + Keys: append(append([]string{}, identityKeys...), + string(ateattr.ActorOperationNameKey), + string(ateattr.ActorStateKey), + string(ateattr.FailureReasonKey), + string(ateattr.FailureDomainKey)), + } +) + +// BuildRecord turns the stdout record into its OTLP form. Attributes carry +// everything machine-readable, so the body stays the display string. +// +// It sets no trace context: the SDK lifts that from ctx onto the record's own +// TraceId/SpanId fields. +func BuildRecord(ev Event, t time.Time, attrs []slog.Attr) log.Record { + var rec log.Record + rec.SetEventName(ev.Name) + rec.SetTimestamp(t) + rec.SetSeverity(ev.Severity) + rec.SetBody(log.StringValue(ev.Body)) + + kvs := make([]log.KeyValue, 0, len(attrs)) + for _, a := range attrs { + kvs = append(kvs, log.String(a.Key, a.Value.String())) + } + rec.AddAttributes(kvs...) + return rec +} + +// Emitter writes events through one log.Logger. Tests construct one directly, so +// they need no global provider and can run in parallel. +type Emitter struct { + logger log.Logger +} + +func NewEmitter(lp log.LoggerProvider) *Emitter { + return &Emitter{logger: lp.Logger(ScopeName)} +} + +// Emit records ev. attrs is the same slice the caller gave its stdout record. +// It is a no-op, and cheap, until InitLogging installs a provider. +func (e *Emitter) Emit(ctx context.Context, ev Event, attrs []slog.Attr) { + params := log.EnabledParameters{Severity: ev.Severity, EventName: ev.Name} + if !e.logger.Enabled(ctx, params) { + return + } + e.logger.Emit(ctx, BuildRecord(ev, time.Now(), attrs)) +} + +// The global provider delegates, so a Logger taken before InitLogging still +// reaches the one it installs. +var defaultEmitter = sync.OnceValue(func() *Emitter { + return NewEmitter(global.GetLoggerProvider()) +}) + +// Emit records ev through the process-wide provider. +func Emit(ctx context.Context, ev Event, attrs []slog.Attr) { + defaultEmitter().Emit(ctx, ev, attrs) +} diff --git a/internal/actorevent/actorevent_test.go b/internal/actorevent/actorevent_test.go new file mode 100644 index 0000000000..be48adf487 --- /dev/null +++ b/internal/actorevent/actorevent_test.go @@ -0,0 +1,235 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package actorevent_test + +import ( + "context" + "log/slog" + "slices" + "testing" + "time" + + "go.opentelemetry.io/otel/log" + sdklog "go.opentelemetry.io/otel/sdk/log" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + + "github.com/agent-substrate/substrate/internal/actorevent" + "github.com/agent-substrate/substrate/internal/ateattr" + "github.com/agent-substrate/substrate/internal/resources" +) + +const ( + testAtespace = "ate-demo-counter" + testActorName = "counter-1" + testActorUID = "8f2a1c4e6b0d47f1" + testTemplateName = "counter" + testReason = "ACTOR_EXITED" +) + +func testAttribution() resources.ActorAttribution { + return resources.ActorAttribution{ + Ref: resources.ActorRef{Atespace: testAtespace, Name: testActorName}, + UID: testActorUID, + TemplateAtespace: testAtespace, + TemplateName: testTemplateName, + } +} + +// stateChangedAttrs mirrors what controlapi.logActorState builds. +func stateChangedAttrs(state string) []slog.Attr { + return append(ateattr.ActorLogAttrs(testAttribution()), + slog.String(string(ateattr.ActorOperationNameKey), ateattr.OperationResume), + slog.String(string(ateattr.ActorStateKey), state)) +} + +// crashedAttrs mirrors what controlapi.logActorCrashed builds. +func crashedAttrs() []slog.Attr { + attrs := append(ateattr.ActorLogAttrs(testAttribution()), + slog.String(string(ateattr.ActorOperationNameKey), ateattr.OperationResume), + slog.String(string(ateattr.ActorStateKey), ateattr.ActorStateCrashed)) + return append(attrs, ateattr.FailureLogAttrs(testReason)...) +} + +func recordAttrs(rec log.Record) map[string]string { + got := make(map[string]string, rec.AttributesLen()) + rec.WalkAttributes(func(kv log.KeyValue) bool { + got[kv.Key] = kv.Value.String() + return true + }) + return got +} + +func TestBuildRecord(t *testing.T) { + t.Parallel() + + now := time.Date(2026, 9, 15, 12, 0, 0, 0, time.UTC) + + tests := []struct { + name string + event actorevent.Event + attrs []slog.Attr + wantName string + wantBody string + wantSev log.Severity + wantVals map[string]string + }{ + { + name: "state changed", + event: actorevent.StateChanged, + attrs: stateChangedAttrs(ateattr.ActorStateRunning), + wantName: "ate.actor.state_changed", + wantBody: actorevent.StateChangedBody, + wantSev: log.SeverityInfo, + wantVals: map[string]string{ + string(ateattr.ActorStateKey): ateattr.ActorStateRunning, + string(ateattr.ActorOperationNameKey): ateattr.OperationResume, + string(ateattr.ActorUIDKey): testActorUID, + }, + }, + { + name: "deleted is a state, not a name of its own", + event: actorevent.StateChanged, + attrs: stateChangedAttrs(ateattr.ActorStateDeleted), + wantName: "ate.actor.state_changed", + wantBody: actorevent.StateChangedBody, + wantSev: log.SeverityInfo, + wantVals: map[string]string{ + string(ateattr.ActorStateKey): ateattr.ActorStateDeleted, + }, + }, + { + name: "crashed", + event: actorevent.Crashed, + attrs: crashedAttrs(), + wantName: "ate.actor.crashed", + wantBody: actorevent.CrashedBody, + wantSev: log.SeverityError, + wantVals: map[string]string{ + string(ateattr.ActorStateKey): ateattr.ActorStateCrashed, + string(ateattr.FailureReasonKey): testReason, + string(ateattr.FailureDomainKey): ateattr.FailureDomain(testReason), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + rec := actorevent.BuildRecord(tt.event, now, tt.attrs) + + if got := rec.EventName(); got != tt.wantName { + t.Errorf("EventName() = %q, want %q", got, tt.wantName) + } + if got := rec.Body().String(); got != tt.wantBody { + t.Errorf("Body() = %q, want %q", got, tt.wantBody) + } + if got := rec.Severity(); got != tt.wantSev { + t.Errorf("Severity() = %v, want %v", got, tt.wantSev) + } + if got := rec.Timestamp(); !got.Equal(now) { + t.Errorf("Timestamp() = %v, want %v", got, now) + } + // SeverityText is the stdout format's concern, not the event's. + if got := rec.SeverityText(); got != "" { + t.Errorf("SeverityText() = %q, want empty", got) + } + + got := recordAttrs(rec) + for key, want := range tt.wantVals { + if got[key] != want { + t.Errorf("attribute %q = %q, want %q", key, got[key], want) + } + } + + // The event name promises a fixed shape, so the emitted set and the + // declared set must match both ways. + for _, key := range tt.event.Keys { + if _, ok := got[key]; !ok { + t.Errorf("declared key %q is missing from the record", key) + } + } + for key := range got { + if !slices.Contains(tt.event.Keys, key) { + t.Errorf("record carries %q, which %s does not declare", key, tt.event.Name) + } + } + }) + } +} + +// memExporter collects records in memory. Small enough to keep here rather than +// vendoring the SDK's test package. +type memExporter struct { + records []sdklog.Record +} + +func (e *memExporter) Export(_ context.Context, records []sdklog.Record) error { + e.records = append(e.records, records...) + return nil +} +func (e *memExporter) Shutdown(context.Context) error { return nil } +func (e *memExporter) ForceFlush(context.Context) error { return nil } + +func TestEmitCarriesTraceContext(t *testing.T) { + t.Parallel() + + exp := &memExporter{} + lp := sdklog.NewLoggerProvider(sdklog.WithProcessor(sdklog.NewSimpleProcessor(exp))) + t.Cleanup(func() { _ = lp.Shutdown(context.Background()) }) + + // A local tracer provider, never the global, so this stays parallel-safe. + tp := sdktrace.NewTracerProvider(sdktrace.WithSampler(sdktrace.AlwaysSample())) + t.Cleanup(func() { _ = tp.Shutdown(context.Background()) }) + ctx, span := tp.Tracer("test").Start(context.Background(), "test") + defer span.End() + + actorevent.NewEmitter(lp).Emit(ctx, actorevent.StateChanged, stateChangedAttrs(ateattr.ActorStateRunning)) + + if len(exp.records) != 1 { + t.Fatalf("exported %d records, want 1", len(exp.records)) + } + rec := exp.records[0] + + sc := span.SpanContext() + if got := rec.TraceID(); got != sc.TraceID() { + t.Errorf("TraceID() = %v, want %v", got, sc.TraceID()) + } + if got := rec.SpanID(); got != sc.SpanID() { + t.Errorf("SpanID() = %v, want %v", got, sc.SpanID()) + } + + // Trace context belongs on the record's own fields. The stdout copy carries + // it as attributes; the OTLP copy must not, or it is there twice. + rec.WalkAttributes(func(kv log.KeyValue) bool { + switch kv.Key { + case ateattr.LogTraceIDField, ateattr.LogSpanIDField, ateattr.LogTraceFlagsField: + t.Errorf("record carries trace context as the attribute %q", kv.Key) + } + return true + }) + + if got := rec.ObservedTimestamp(); got.IsZero() { + t.Error("ObservedTimestamp() is zero, want the SDK to have set it") + } +} + +func TestEmitIsANoOpWithoutAProvider(t *testing.T) { + t.Parallel() + + // The package default resolves the global provider, which no test installs. + // This asserts it does not panic rather than that it drops the record. + actorevent.Emit(context.Background(), actorevent.StateChanged, stateChangedAttrs(ateattr.ActorStateRunning)) +} diff --git a/internal/e2e/collector_metrics.go b/internal/e2e/collector_metrics.go index 498197885d..5b3f184ecb 100644 --- a/internal/e2e/collector_metrics.go +++ b/internal/e2e/collector_metrics.go @@ -19,6 +19,7 @@ import ( "fmt" "io" "net/http" + "strconv" "strings" "time" @@ -154,6 +155,52 @@ func MissingPlatformMetrics(scrape string, prefixes []string) []string { return missing } +// LifecycleEventMetric is the count connector's view of the actor lifecycle +// events. Reading it rather than a log store keeps this check on the metrics +// harness: kind has no place to query log records. +const LifecycleEventMetric = "substrate_actor_state_changes" + +// LifecycleEventCounts returns the count the collector holds for each +// ate.actor.state. The counter is cumulative and the collector outlives any one +// test, so compare two reads rather than asserting a state is merely present: +// a stale count from an earlier run would pass a presence check even with the +// exporter turned off. +// +// One state can appear on several lines, one per emitting ateapi instance, so +// the counts are summed. +func LifecycleEventCounts(scrape string) map[string]float64 { + counts := map[string]float64{} + for _, line := range strings.Split(scrape, "\n") { + name := metricNameFromLine(line) + if name != LifecycleEventMetric && !strings.HasPrefix(name, LifecycleEventMetric+"_") { + continue + } + state := promLabelValue(line, "ate_actor_state") + if state == "" { + continue + } + fields := strings.Fields(line) + v, err := strconv.ParseFloat(fields[len(fields)-1], 64) + if err != nil { + continue + } + counts[state] += v + } + return counts +} + +// StatesNotAdvanced returns the states whose count did not rise between the two +// reads. An empty result means every state was emitted during the window. +func StatesNotAdvanced(before, after map[string]float64, states []string) []string { + var stale []string + for _, s := range states { + if after[s] <= before[s] { + stale = append(stale, s) + } + } + return stale +} + // CollectorHasService reports whether any named service has pushed telemetry to // the collector. Its prometheus exporter maps each pushed resource's service.name // onto the job label, so a service that has exported at least one data point diff --git a/internal/e2e/collector_metrics_test.go b/internal/e2e/collector_metrics_test.go index 6c41090ba6..b0efb72843 100644 --- a/internal/e2e/collector_metrics_test.go +++ b/internal/e2e/collector_metrics_test.go @@ -153,3 +153,107 @@ func TestTargetInfoLabel(t *testing.T) { }) } } + +// The count connector's own exposition shape: a _total suffix, the two keys the +// connector is configured with, and an unrelated series to reject. +const sampleLifecycleScrape = `# HELP substrate_actor_state_changes_total Actor state change events received, by state. +# TYPE substrate_actor_state_changes_total counter +substrate_actor_state_changes_total{service_name="ateapi",ate_actor_state="resuming"} 2 +substrate_actor_state_changes_total{service_name="ateapi",ate_actor_state="running"} 2 +substrate_actor_state_changes_total{service_name="ateapi",instance="other",ate_actor_state="running"} 3 +substrate_actor_state_changes_total{service_name="ateapi",ate_actor_state="crashed"} 1 +# TYPE substrate_spans_total counter +substrate_spans_total{service_name="ateapi"} 40 +` + +func TestLifecycleEventCounts(t *testing.T) { + tests := []struct { + name string + scrape string + want map[string]float64 + }{ + { + name: "every counted state, summed over instances", + scrape: sampleLifecycleScrape, + want: map[string]float64{"resuming": 2, "running": 5, "crashed": 1}, + }, + { + name: "no events counted", + scrape: sampleScrape, + want: map[string]float64{}, + }, + { + name: "empty scrape", + scrape: "", + want: map[string]float64{}, + }, + { + name: "a series with no state label contributes nothing", + scrape: "substrate_actor_state_changes_total{service_name=\"ateapi\"} 3\n", + want: map[string]float64{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := LifecycleEventCounts(tt.scrape) + if len(got) != len(tt.want) { + t.Fatalf("LifecycleEventCounts() = %v, want %v", got, tt.want) + } + for state, want := range tt.want { + if got[state] != want { + t.Errorf("LifecycleEventCounts()[%q] = %v, want %v", state, got[state], want) + } + } + }) + } +} + +func TestStatesNotAdvanced(t *testing.T) { + tests := []struct { + name string + before, after map[string]float64 + states []string + want []string + }{ + { + name: "every state advanced", + before: map[string]float64{"running": 1, "crashed": 0}, + after: map[string]float64{"running": 3, "crashed": 1}, + states: []string{"running", "crashed"}, + }, + { + name: "a stale count from an earlier run is not an advance", + before: map[string]float64{"running": 3, "crashed": 1}, + after: map[string]float64{"running": 3, "crashed": 1}, + states: []string{"running", "crashed"}, + want: []string{"running", "crashed"}, + }, + { + name: "a state that never appeared", + before: map[string]float64{}, + after: map[string]float64{"running": 2}, + states: []string{"running", "suspended"}, + want: []string{"suspended"}, + }, + { + name: "first observation of a state counts as an advance", + before: map[string]float64{}, + after: map[string]float64{"running": 1}, + states: []string{"running"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := StatesNotAdvanced(tt.before, tt.after, tt.states) + if len(got) != len(tt.want) { + t.Fatalf("StatesNotAdvanced() = %v, want %v", got, tt.want) + } + for i := range got { + if got[i] != tt.want[i] { + t.Errorf("StatesNotAdvanced() = %v, want %v", got, tt.want) + break + } + } + }) + } +} diff --git a/internal/e2e/suites/metrics/metrics_test.go b/internal/e2e/suites/metrics/metrics_test.go index b871f39a80..1f69fe2942 100644 --- a/internal/e2e/suites/metrics/metrics_test.go +++ b/internal/e2e/suites/metrics/metrics_test.go @@ -43,6 +43,16 @@ func TestPlatformMetricsEmitted(t *testing.T) { tmpl := e2e.SubstrateCounterFixture() actorID := fmt.Sprintf("metrics-probe-%d", time.Now().UnixNano()) + // The lifecycle event counters are cumulative and the collector outlives this + // test, so take a baseline before driving anything. Asserting the states are + // merely present would pass on a previous run's counts, even with the + // exporter switched off. + baselineScrape, err := e2e.ScrapeCollectorMetrics(ctx) + if err != nil { + t.Fatalf("ScrapeCollectorMetrics for the lifecycle baseline: %v", err) + } + lifecycleBaseline := e2e.LifecycleEventCounts(baselineScrape) + // CreateActor requires the atespace to exist first; ignore AlreadyExists. _, _ = clients.SubstrateAPI.CreateAtespace(ctx, &ateapipb.CreateAtespaceRequest{ Atespace: &ateapipb.Atespace{Metadata: &ateapipb.ResourceMetadata{Name: metricsAtespace}}, @@ -87,11 +97,22 @@ func TestPlatformMetricsEmitted(t *testing.T) { // because it deletes the worker pod. triggerActorCrash(t, ctx, clients, actorID) + // The actor lifecycle events the steps above must have produced. The crash + // is the one state only ateapi can report, so it is what proves the events + // carry more than the ateom plane could. + wantStates := []string{ + ateattr.ActorStateResuming, + ateattr.ActorStateRunning, + ateattr.ActorStateSuspended, + ateattr.ActorStateCrashed, + } + deadline := time.Now().Add(2 * time.Minute) dataplane := e2e.CurrentAtenetDataplane() prefixes := dataplane.PlatformMetricPrefixes(e2e.PlatformMetricPrefixes) var missing []string - var ateomSeen, controllerSeen, routeDurationSeen bool + var ateomSeen, controllerSeen, routeDurationSeen, lifecycleSeen bool + var missingStates []string var lastLabelErr error for time.Now().Before(deadline) { scrape, err := e2e.ScrapeCollectorMetrics(ctx) @@ -103,6 +124,8 @@ func TestPlatformMetricsEmitted(t *testing.T) { if err != nil { t.Fatalf("checking route-duration metric: %v", err) } + missingStates = e2e.StatesNotAdvanced(lifecycleBaseline, e2e.LifecycleEventCounts(scrape), wantStates) + lifecycleSeen = len(missingStates) == 0 ateomSeen = e2e.CollectorHasService(scrape, "ateom-gvisor", "ateom-microvm") // atecontroller bridges controller-runtime's Prometheus registry onto its OTLP // reader, so the reconcile families are what prove the bridge, not just that @@ -111,7 +134,7 @@ func TestPlatformMetricsEmitted(t *testing.T) { controllerSeen = e2e.CollectorHasService(scrape, "atecontroller") && strings.Contains(scrape, "controller_runtime_") - if len(missing) == 0 && ateomSeen && controllerSeen && routeDurationSeen { + if len(missing) == 0 && ateomSeen && controllerSeen && routeDurationSeen && lifecycleSeen { var errs []string // Verify ate_workerpool_desired_workers carries required namespaced attributes. @@ -304,11 +327,11 @@ func TestPlatformMetricsEmitted(t *testing.T) { } if lastLabelErr != nil { - t.Fatalf("platform telemetry validation failed: missing metrics %v, ateom pushed=%v, atecontroller pushed=%v, error detail: %v", - missing, ateomSeen, controllerSeen, lastLabelErr) + t.Fatalf("platform telemetry validation failed: missing metrics %v, missing lifecycle states %v, ateom pushed=%v, atecontroller pushed=%v, error detail: %v", + missing, missingStates, ateomSeen, controllerSeen, lastLabelErr) } - t.Fatalf("platform telemetry validation failed: collector missing metrics %v, AgentGateway route duration seen=%v, ateom pushed=%v, atecontroller pushed=%v", - missing, routeDurationSeen, ateomSeen, controllerSeen) + t.Fatalf("platform telemetry validation failed: collector missing metrics %v, missing lifecycle states %v, AgentGateway route duration seen=%v, ateom pushed=%v, atecontroller pushed=%v", + missing, missingStates, routeDurationSeen, ateomSeen, controllerSeen) } func triggerActorCrash(t *testing.T, ctx context.Context, clients *e2e.Clients, actorID string) { diff --git a/internal/serverboot/logging.go b/internal/serverboot/logging.go new file mode 100644 index 0000000000..77c601c006 --- /dev/null +++ b/internal/serverboot/logging.go @@ -0,0 +1,135 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package serverboot + +import ( + "context" + "fmt" + "log/slog" + "os" + "strings" + + "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc" + "go.opentelemetry.io/otel/log/global" + sdklog "go.opentelemetry.io/otel/sdk/log" +) + +const logsExporterEnv = "OTEL_LOGS_EXPORTER" + +// LogsExporter says where log records go. Only the records a component emits +// through the OTel logs API are affected; slog to stdout is always on. +type LogsExporter string + +const ( + // LogsExporterNone drops the records. It is the component default, so an + // environment that has not opted in changes nothing. + LogsExporterNone LogsExporter = "none" + // LogsExporterOTLP sends to OTEL_EXPORTER_OTLP_ENDPOINT. + LogsExporterOTLP LogsExporter = "otlp" +) + +// ResolveLogsExporter applies OTEL_LOGS_EXPORTER on top of the component +// default. An unrecognized value keeps the default and logs, rather than +// failing startup over a telemetry setting. +// +// The default is none rather than the spec's otlp while this rolls out, so +// turning it on is one ConfigMap key per environment. +func ResolveLogsExporter(ctx context.Context, def LogsExporter) LogsExporter { + value, isSet := os.LookupEnv(logsExporterEnv) + resolved, err := resolveLogsExporter(value, isSet, def) + if err != nil { + slog.WarnContext(ctx, "Invalid logs exporter environment, keeping the component default", + slog.String("exporter", value), + slog.String("default", string(def)), + slog.Any("err", err)) + } + return resolved +} + +// resolveLogsExporter accepts otlp and none. Any error means def was kept. +func resolveLogsExporter(value string, isSet bool, def LogsExporter) (LogsExporter, error) { + if !isSet { + return def, nil + } + value = strings.ToLower(strings.TrimSpace(value)) + // Treat set-but-empty as unset: templated manifests can render empty env vars. + if value == "" { + return def, nil + } + + switch LogsExporter(value) { + case LogsExporterNone: + return LogsExporterNone, nil + case LogsExporterOTLP: + return LogsExporterOTLP, nil + } + return def, fmt.Errorf("unsupported %s %q", logsExporterEnv, value) +} + +// LoggingOptions configures InitLogging. +type LoggingOptions struct { + // ServiceName is required; populates resource.semconv ServiceName. + ServiceName string + // Exporter is required. Build it with ResolveLogsExporter so + // OTEL_LOGS_EXPORTER overrides the component default. + Exporter LogsExporter +} + +// InitLogging registers a global LoggerProvider for the records components emit +// through internal/actorevent. +// +// It returns (nil, nil) when the exporter is none, and leaves the OTel global +// alone: emitting then costs one Enabled check. Guard the shutdown on a nil +// provider, unlike InitTracing which always returns one. +// +// The processor batches. These records sit on the actor resume and suspend +// path, and exporting inside the emit call would put a blocking gRPC round trip +// there, serialized across every emitting goroutine, so an unreachable +// collector would surface as control-plane latency. The cost is a bounded loss +// window on an ungraceful exit, where the same record is still on stdout. +func InitLogging(ctx context.Context, opts LoggingOptions) (*sdklog.LoggerProvider, error) { + if opts.ServiceName == "" { + return nil, fmt.Errorf("LoggingOptions.ServiceName is required") + } + if opts.Exporter == "" { + return nil, fmt.Errorf("LoggingOptions.Exporter is required") + } + if opts.Exporter == LogsExporterNone { + slog.InfoContext(ctx, "Logs exporter disabled", slog.String("exporter", string(opts.Exporter))) + return nil, nil + } + + res, err := newResource(ctx, opts.ServiceName) + if err != nil { + return nil, fmt.Errorf("create logger resource: %w", err) + } + + exporter, err := otlploggrpc.New(ctx, + // Matches the trace and metric exporters: GKE managed telemetry does not + // support validating the TLS certs of the collector. + otlploggrpc.WithInsecure(), + ) + if err != nil { + return nil, fmt.Errorf("create OTLP log exporter: %w", err) + } + + lp := sdklog.NewLoggerProvider( + sdklog.WithResource(res), + sdklog.WithProcessor(sdklog.NewBatchProcessor(exporter)), + ) + global.SetLoggerProvider(lp) + slog.InfoContext(ctx, "Logging initialized", slog.String("exporter", string(opts.Exporter))) + return lp, nil +} diff --git a/internal/serverboot/logging_test.go b/internal/serverboot/logging_test.go new file mode 100644 index 0000000000..eddaa3857f --- /dev/null +++ b/internal/serverboot/logging_test.go @@ -0,0 +1,169 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package serverboot + +import ( + "context" + "testing" + + "go.opentelemetry.io/otel/log/global" +) + +func TestResolveLogsExporter(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + value string + isSet bool + def LogsExporter + want LogsExporter + wantErr bool + }{ + { + name: "unset keeps the default", + def: LogsExporterNone, + want: LogsExporterNone, + }, + { + name: "set but empty reads as unset", + value: "", + isSet: true, + def: LogsExporterNone, + want: LogsExporterNone, + }, + { + name: "whitespace only reads as unset", + value: " ", + isSet: true, + def: LogsExporterOTLP, + want: LogsExporterOTLP, + }, + { + name: "otlp", + value: "otlp", + isSet: true, + def: LogsExporterNone, + want: LogsExporterOTLP, + }, + { + name: "none overrides an otlp default", + value: "none", + isSet: true, + def: LogsExporterOTLP, + want: LogsExporterNone, + }, + { + name: "mixed case and padding", + value: " OTLP\t", + isSet: true, + def: LogsExporterNone, + want: LogsExporterOTLP, + }, + { + name: "unsupported value keeps the default", + value: "console", + isSet: true, + def: LogsExporterNone, + want: LogsExporterNone, + wantErr: true, + }, + { + name: "unsupported value keeps a non-default default", + value: "otlp/http", + isSet: true, + def: LogsExporterOTLP, + want: LogsExporterOTLP, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + got, err := resolveLogsExporter(tt.value, tt.isSet, tt.def) + if (err != nil) != tt.wantErr { + t.Fatalf("resolveLogsExporter(%q, %v, %q) error = %v, wantErr %v", tt.value, tt.isSet, tt.def, err, tt.wantErr) + } + if got != tt.want { + t.Errorf("resolveLogsExporter(%q, %v, %q) = %q, want %q", tt.value, tt.isSet, tt.def, got, tt.want) + } + }) + } +} + +func TestResolveLogsExporterReadsEnv(t *testing.T) { + ctx := context.Background() + + t.Setenv(logsExporterEnv, "otlp") + if got := ResolveLogsExporter(ctx, LogsExporterNone); got != LogsExporterOTLP { + t.Errorf("ResolveLogsExporter() = %q, want %q", got, LogsExporterOTLP) + } + + t.Setenv(logsExporterEnv, "not_an_exporter") + if got := ResolveLogsExporter(ctx, LogsExporterNone); got != LogsExporterNone { + t.Errorf("ResolveLogsExporter() on an invalid value = %q, want the default %q", got, LogsExporterNone) + } +} + +func TestInitLoggingDisabled(t *testing.T) { + // global.SetLoggerProvider has no undo, so assert on the provider this + // returns rather than on the global. A nil provider is what proves + // InitLogging left the global alone. + before := global.GetLoggerProvider() + + lp, err := InitLogging(context.Background(), LoggingOptions{ + ServiceName: "test", + Exporter: LogsExporterNone, + }) + if err != nil { + t.Fatalf("InitLogging() error = %v", err) + } + if lp != nil { + t.Errorf("InitLogging() with the exporter disabled = %v, want nil", lp) + } + if got := global.GetLoggerProvider(); got != before { + t.Errorf("InitLogging() with the exporter disabled replaced the global provider") + } +} + +func TestInitLoggingRequiresOptions(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + opts LoggingOptions + }{ + { + name: "no service name", + opts: LoggingOptions{Exporter: LogsExporterOTLP}, + }, + { + name: "no exporter", + opts: LoggingOptions{ServiceName: "test"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + if _, err := InitLogging(context.Background(), tt.opts); err == nil { + t.Errorf("InitLogging(%+v) error = nil, want an error", tt.opts) + } + }) + } +} diff --git a/manifests/ate-install/kind/ate-otel-config.yaml b/manifests/ate-install/kind/ate-otel-config.yaml index 0374856911..cc5314f991 100644 --- a/manifests/ate-install/kind/ate-otel-config.yaml +++ b/manifests/ate-install/kind/ate-otel-config.yaml @@ -29,6 +29,10 @@ # On ate-controller both do double duty: the controller also propagates them to # the ateom worker pods it creates, which have no other way to receive them. # Kind only; production keeps the SDK defaults. +# +# OTEL_LOGS_EXPORTER turns on the actor lifecycle events. serverboot defaults it +# to none, so the base ConfigMap leaves it unset and no other environment sends +# log records yet. Only ateapi has a LoggerProvider today. apiVersion: v1 kind: ConfigMap metadata: @@ -38,3 +42,4 @@ data: OTEL_EXPORTER_OTLP_ENDPOINT: http://opentelemetry-collector.otel-system.svc:4317 OTEL_METRIC_EXPORT_INTERVAL: "10000" OTEL_METRIC_EXPORT_TIMEOUT: "10000" + OTEL_LOGS_EXPORTER: otlp diff --git a/manifests/ate-install/kind/otel-collector.yaml b/manifests/ate-install/kind/otel-collector.yaml index 0aab804597..0abe187dd7 100644 --- a/manifests/ate-install/kind/otel-collector.yaml +++ b/manifests/ate-install/kind/otel-collector.yaml @@ -74,6 +74,20 @@ data: - key: service.name default_value: unknown + # The actor lifecycle events. The key is ate.actor.state and not the + # event name: the connector reads the attributes of the item, and the + # event name is a top level field of the record, which would need a + # transform in front of this. The state is bounded and is the more + # useful of the two anyway. + logs: + substrate_actor_state_changes: + description: Actor state change events received, by state. + attributes: + - key: service.name + default_value: unknown + - key: ate.actor.state + default_value: unknown + exporters: otlp/jaeger: endpoint: jaeger.otel-system.svc.cluster.local:4317 @@ -94,15 +108,28 @@ data: processors: [batch] exporters: [prometheus, debug] - # The count pipelines read the same receiver as the two pipelines - # above, and they only count. The connector declares MutatesData - # false, thus it changes nothing that reaches Jaeger or Prometheus. + # The actor lifecycle events. debug is the only exporter: kind has no + # log store, and the e2e read-back goes through the count connector + # below. Never put a sampler or a filter here. A consumer takes the + # last event for an actor, thus a dropped record reports a stale state + # with no sign that anything is missing. + logs: + receivers: [otlp] + processors: [batch] + exporters: [debug] + + # The count pipelines read the same receiver as the pipelines above, + # and they only count. The connector declares MutatesData false, thus + # it changes nothing that reaches Jaeger or Prometheus. traces/count: receivers: [otlp] exporters: [count] metrics/count: receivers: [otlp] exporters: [count] + logs/count: + receivers: [otlp] + exporters: [count] # The counts that the connector makes, out to the Prometheus exporter. metrics/count-out: From bb1bebb1b4aaabd3bbfd619647bc24b73a27815b Mon Sep 17 00:00:00 2001 From: krisztianfekete Date: Fri, 18 Sep 2026 16:25:14 +0200 Subject: [PATCH 3/3] keep attribute kinds in the otlp copy and scope the event count --- internal/actorevent/actorevent.go | 23 +++++++++++- internal/actorevent/actorevent_test.go | 37 +++++++++++++++++++ .../ate-install/kind/otel-collector.yaml | 12 +++--- 3 files changed, 65 insertions(+), 7 deletions(-) diff --git a/internal/actorevent/actorevent.go b/internal/actorevent/actorevent.go index 7410060bbe..b1d15fc06d 100644 --- a/internal/actorevent/actorevent.go +++ b/internal/actorevent/actorevent.go @@ -105,12 +105,33 @@ func BuildRecord(ev Event, t time.Time, attrs []slog.Attr) log.Record { kvs := make([]log.KeyValue, 0, len(attrs)) for _, a := range attrs { - kvs = append(kvs, log.String(a.Key, a.Value.String())) + kvs = append(kvs, log.KeyValue{Key: a.Key, Value: logValue(a.Value)}) } rec.AddAttributes(kvs...) return rec } +// logValue keeps the kind slog's JSON handler writes, so the two copies match. +func logValue(v slog.Value) log.Value { + switch v.Kind() { + case slog.KindString: + return log.StringValue(v.String()) + case slog.KindInt64: + return log.Int64Value(v.Int64()) + case slog.KindUint64: + return log.Int64Value(int64(v.Uint64())) + case slog.KindFloat64: + return log.Float64Value(v.Float64()) + case slog.KindBool: + return log.BoolValue(v.Bool()) + case slog.KindDuration: + // nanoseconds, not "1.5s" + return log.Int64Value(int64(v.Duration())) + default: + return log.StringValue(v.String()) + } +} + // Emitter writes events through one log.Logger. Tests construct one directly, so // they need no global provider and can run in parallel. type Emitter struct { diff --git a/internal/actorevent/actorevent_test.go b/internal/actorevent/actorevent_test.go index be48adf487..e8f1702172 100644 --- a/internal/actorevent/actorevent_test.go +++ b/internal/actorevent/actorevent_test.go @@ -233,3 +233,40 @@ func TestEmitIsANoOpWithoutAProvider(t *testing.T) { // This asserts it does not panic rather than that it drops the record. actorevent.Emit(context.Background(), actorevent.StateChanged, stateChangedAttrs(ateattr.ActorStateRunning)) } + +func TestBuildRecordKeepsValueKinds(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + attr slog.Attr + want log.Value + }{ + {"string", slog.String("k", "v"), log.StringValue("v")}, + {"int", slog.Int64("k", 7), log.Int64Value(7)}, + {"uint", slog.Uint64("k", 7), log.Int64Value(7)}, + {"float", slog.Float64("k", 1.5), log.Float64Value(1.5)}, + {"bool", slog.Bool("k", true), log.BoolValue(true)}, + {"duration is nanoseconds, as in the stdout copy", slog.Duration("k", 1500*time.Millisecond), log.Int64Value(1_500_000_000)}, + {"anything else falls back to its string form", slog.Any("k", struct{}{}), log.StringValue("{}")}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + rec := actorevent.BuildRecord(actorevent.StateChanged, time.Now(), []slog.Attr{tt.attr}) + var got log.Value + rec.WalkAttributes(func(kv log.KeyValue) bool { + got = kv.Value + return false + }) + if got.Kind() != tt.want.Kind() { + t.Fatalf("kind = %v, want %v", got.Kind(), tt.want.Kind()) + } + if got.String() != tt.want.String() { + t.Errorf("value = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/manifests/ate-install/kind/otel-collector.yaml b/manifests/ate-install/kind/otel-collector.yaml index 0abe187dd7..e482ab415e 100644 --- a/manifests/ate-install/kind/otel-collector.yaml +++ b/manifests/ate-install/kind/otel-collector.yaml @@ -85,8 +85,8 @@ data: attributes: - key: service.name default_value: unknown + # No default: drops workload logs, which have no such key. - key: ate.actor.state - default_value: unknown exporters: otlp/jaeger: @@ -108,11 +108,11 @@ data: processors: [batch] exporters: [prometheus, debug] - # The actor lifecycle events. debug is the only exporter: kind has no - # log store, and the e2e read-back goes through the count connector - # below. Never put a sampler or a filter here. A consumer takes the - # last event for an actor, thus a dropped record reports a stale state - # with no sign that anything is missing. + # Every ate event, plus any workload that sends OTLP logs. debug is the + # only exporter: kind has no log store, and the e2e read-back goes + # through the count connector below. Never put a sampler or a filter + # here. A consumer takes the last event for an actor, thus a dropped + # record reports a stale state with no sign that anything is missing. logs: receivers: [otlp] processors: [batch]