Initial QSfera import
This commit is contained in:
+231
@@ -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.
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
# OTLP Trace Exporter
|
||||
|
||||
[](https://pkg.go.dev/go.opentelemetry.io/otel/exporters/otlp/otlptrace)
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package otlptrace // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
tracepb "go.opentelemetry.io/proto/otlp/trace/v1"
|
||||
)
|
||||
|
||||
// Client manages connections to the collector, handles the
|
||||
// transformation of data into wire format, and the transmission of that
|
||||
// data to the collector.
|
||||
type Client interface {
|
||||
// DO NOT CHANGE: any modification will not be backwards compatible and
|
||||
// must never be done outside of a new major release.
|
||||
|
||||
// Start should establish connection(s) to endpoint(s). It is
|
||||
// called just once by the exporter, so the implementation
|
||||
// does not need to worry about idempotence and locking.
|
||||
Start(ctx context.Context) error
|
||||
// DO NOT CHANGE: any modification will not be backwards compatible and
|
||||
// must never be done outside of a new major release.
|
||||
|
||||
// Stop should close the connections. The function is called
|
||||
// only once by the exporter, so the implementation does not
|
||||
// need to worry about idempotence, but it may be called
|
||||
// concurrently with UploadTraces, so proper
|
||||
// locking is required. The function serves as a
|
||||
// synchronization point - after the function returns, the
|
||||
// process of closing connections is assumed to be finished.
|
||||
Stop(ctx context.Context) error
|
||||
// DO NOT CHANGE: any modification will not be backwards compatible and
|
||||
// must never be done outside of a new major release.
|
||||
|
||||
// UploadTraces should transform the passed traces to the wire
|
||||
// format and send it to the collector. May be called
|
||||
// concurrently.
|
||||
UploadTraces(ctx context.Context, protoSpans []*tracepb.ResourceSpans) error
|
||||
// DO NOT CHANGE: any modification will not be backwards compatible and
|
||||
// must never be done outside of a new major release.
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/*
|
||||
Package otlptrace contains abstractions for OTLP span exporters.
|
||||
See the official OTLP span exporter implementations:
|
||||
- [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc],
|
||||
- [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp].
|
||||
*/
|
||||
package otlptrace // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package otlptrace // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/tracetransform"
|
||||
tracesdk "go.opentelemetry.io/otel/sdk/trace"
|
||||
)
|
||||
|
||||
var errAlreadyStarted = errors.New("already started")
|
||||
|
||||
// Exporter exports trace data in the OTLP wire format.
|
||||
type Exporter struct {
|
||||
client Client
|
||||
|
||||
mu sync.RWMutex
|
||||
started bool
|
||||
|
||||
startOnce sync.Once
|
||||
stopOnce sync.Once
|
||||
}
|
||||
|
||||
// ExportSpans exports a batch of spans.
|
||||
func (e *Exporter) ExportSpans(ctx context.Context, ss []tracesdk.ReadOnlySpan) error {
|
||||
protoSpans := tracetransform.Spans(ss)
|
||||
if len(protoSpans) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := e.client.UploadTraces(ctx, protoSpans)
|
||||
if err != nil {
|
||||
return fmt.Errorf("traces export: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start establishes a connection to the receiving endpoint.
|
||||
func (e *Exporter) Start(ctx context.Context) error {
|
||||
err := errAlreadyStarted
|
||||
e.startOnce.Do(func() {
|
||||
e.mu.Lock()
|
||||
e.started = true
|
||||
e.mu.Unlock()
|
||||
err = e.client.Start(ctx)
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Shutdown flushes all exports and closes all connections to the receiving endpoint.
|
||||
func (e *Exporter) Shutdown(ctx context.Context) error {
|
||||
e.mu.RLock()
|
||||
started := e.started
|
||||
e.mu.RUnlock()
|
||||
|
||||
if !started {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
e.stopOnce.Do(func() {
|
||||
err = e.client.Stop(ctx)
|
||||
e.mu.Lock()
|
||||
e.started = false
|
||||
e.mu.Unlock()
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var _ tracesdk.SpanExporter = (*Exporter)(nil)
|
||||
|
||||
// New constructs a new Exporter and starts it.
|
||||
func New(ctx context.Context, client Client) (*Exporter, error) {
|
||||
exp := NewUnstarted(client)
|
||||
if err := exp.Start(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return exp, nil
|
||||
}
|
||||
|
||||
// NewUnstarted constructs a new Exporter and does not start it.
|
||||
func NewUnstarted(client Client) *Exporter {
|
||||
return &Exporter{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalLog is the marshaling function used by the logging system to represent this Exporter.
|
||||
func (e *Exporter) MarshalLog() any {
|
||||
return struct {
|
||||
Type string
|
||||
Client Client
|
||||
}{
|
||||
Type: "otlptrace",
|
||||
Client: e.client,
|
||||
}
|
||||
}
|
||||
Server/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/tracetransform/attribute.go
Generated
Vendored
+151
@@ -0,0 +1,151 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package tracetransform provides conversion functionality for the otlptrace
|
||||
// exporters.
|
||||
package tracetransform // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/tracetransform"
|
||||
|
||||
import (
|
||||
commonpb "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
)
|
||||
|
||||
// KeyValues transforms a slice of attribute KeyValues into OTLP key-values.
|
||||
func KeyValues(attrs []attribute.KeyValue) []*commonpb.KeyValue {
|
||||
if len(attrs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
out := make([]*commonpb.KeyValue, 0, len(attrs))
|
||||
for _, kv := range attrs {
|
||||
out = append(out, KeyValue(kv))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Iterator transforms an attribute iterator into OTLP key-values.
|
||||
func Iterator(iter attribute.Iterator) []*commonpb.KeyValue {
|
||||
l := iter.Len()
|
||||
if l == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
out := make([]*commonpb.KeyValue, 0, l)
|
||||
for iter.Next() {
|
||||
out = append(out, KeyValue(iter.Attribute()))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ResourceAttributes transforms a Resource OTLP key-values.
|
||||
func ResourceAttributes(res *resource.Resource) []*commonpb.KeyValue {
|
||||
return Iterator(res.Iter())
|
||||
}
|
||||
|
||||
// KeyValue transforms an attribute KeyValue into an OTLP key-value.
|
||||
func KeyValue(kv attribute.KeyValue) *commonpb.KeyValue {
|
||||
return &commonpb.KeyValue{Key: string(kv.Key), Value: Value(kv.Value)}
|
||||
}
|
||||
|
||||
// Value transforms an attribute Value into an OTLP AnyValue.
|
||||
func Value(v attribute.Value) *commonpb.AnyValue {
|
||||
av := new(commonpb.AnyValue)
|
||||
switch v.Type() {
|
||||
case attribute.BOOL:
|
||||
av.Value = &commonpb.AnyValue_BoolValue{
|
||||
BoolValue: v.AsBool(),
|
||||
}
|
||||
case attribute.BOOLSLICE:
|
||||
av.Value = &commonpb.AnyValue_ArrayValue{
|
||||
ArrayValue: &commonpb.ArrayValue{
|
||||
Values: boolSliceValues(v.AsBoolSlice()),
|
||||
},
|
||||
}
|
||||
case attribute.INT64:
|
||||
av.Value = &commonpb.AnyValue_IntValue{
|
||||
IntValue: v.AsInt64(),
|
||||
}
|
||||
case attribute.INT64SLICE:
|
||||
av.Value = &commonpb.AnyValue_ArrayValue{
|
||||
ArrayValue: &commonpb.ArrayValue{
|
||||
Values: int64SliceValues(v.AsInt64Slice()),
|
||||
},
|
||||
}
|
||||
case attribute.FLOAT64:
|
||||
av.Value = &commonpb.AnyValue_DoubleValue{
|
||||
DoubleValue: v.AsFloat64(),
|
||||
}
|
||||
case attribute.FLOAT64SLICE:
|
||||
av.Value = &commonpb.AnyValue_ArrayValue{
|
||||
ArrayValue: &commonpb.ArrayValue{
|
||||
Values: float64SliceValues(v.AsFloat64Slice()),
|
||||
},
|
||||
}
|
||||
case attribute.STRING:
|
||||
av.Value = &commonpb.AnyValue_StringValue{
|
||||
StringValue: v.AsString(),
|
||||
}
|
||||
case attribute.STRINGSLICE:
|
||||
av.Value = &commonpb.AnyValue_ArrayValue{
|
||||
ArrayValue: &commonpb.ArrayValue{
|
||||
Values: stringSliceValues(v.AsStringSlice()),
|
||||
},
|
||||
}
|
||||
case attribute.EMPTY:
|
||||
default:
|
||||
av.Value = &commonpb.AnyValue_StringValue{
|
||||
StringValue: "INVALID",
|
||||
}
|
||||
}
|
||||
return av
|
||||
}
|
||||
|
||||
func boolSliceValues(vals []bool) []*commonpb.AnyValue {
|
||||
converted := make([]*commonpb.AnyValue, len(vals))
|
||||
for i, v := range vals {
|
||||
converted[i] = &commonpb.AnyValue{
|
||||
Value: &commonpb.AnyValue_BoolValue{
|
||||
BoolValue: v,
|
||||
},
|
||||
}
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
func int64SliceValues(vals []int64) []*commonpb.AnyValue {
|
||||
converted := make([]*commonpb.AnyValue, len(vals))
|
||||
for i, v := range vals {
|
||||
converted[i] = &commonpb.AnyValue{
|
||||
Value: &commonpb.AnyValue_IntValue{
|
||||
IntValue: v,
|
||||
},
|
||||
}
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
func float64SliceValues(vals []float64) []*commonpb.AnyValue {
|
||||
converted := make([]*commonpb.AnyValue, len(vals))
|
||||
for i, v := range vals {
|
||||
converted[i] = &commonpb.AnyValue{
|
||||
Value: &commonpb.AnyValue_DoubleValue{
|
||||
DoubleValue: v,
|
||||
},
|
||||
}
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
func stringSliceValues(vals []string) []*commonpb.AnyValue {
|
||||
converted := make([]*commonpb.AnyValue, len(vals))
|
||||
for i, v := range vals {
|
||||
converted[i] = &commonpb.AnyValue{
|
||||
Value: &commonpb.AnyValue_StringValue{
|
||||
StringValue: v,
|
||||
},
|
||||
}
|
||||
}
|
||||
return converted
|
||||
}
|
||||
Generated
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package tracetransform // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/tracetransform"
|
||||
|
||||
import (
|
||||
commonpb "go.opentelemetry.io/proto/otlp/common/v1"
|
||||
|
||||
"go.opentelemetry.io/otel/sdk/instrumentation"
|
||||
)
|
||||
|
||||
func InstrumentationScope(il instrumentation.Scope) *commonpb.InstrumentationScope {
|
||||
if il == (instrumentation.Scope{}) {
|
||||
return nil
|
||||
}
|
||||
return &commonpb.InstrumentationScope{
|
||||
Name: il.Name,
|
||||
Version: il.Version,
|
||||
Attributes: Iterator(il.Attributes.Iter()),
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package tracetransform // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/tracetransform"
|
||||
|
||||
import (
|
||||
resourcepb "go.opentelemetry.io/proto/otlp/resource/v1"
|
||||
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
)
|
||||
|
||||
// Resource transforms a Resource into an OTLP Resource.
|
||||
func Resource(r *resource.Resource) *resourcepb.Resource {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
return &resourcepb.Resource{Attributes: ResourceAttributes(r)}
|
||||
}
|
||||
Generated
Vendored
+221
@@ -0,0 +1,221 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package tracetransform // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal/tracetransform"
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
tracepb "go.opentelemetry.io/proto/otlp/trace/v1"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/sdk/instrumentation"
|
||||
tracesdk "go.opentelemetry.io/otel/sdk/trace"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
// Spans transforms a slice of OpenTelemetry spans into a slice of OTLP
|
||||
// ResourceSpans.
|
||||
func Spans(sdl []tracesdk.ReadOnlySpan) []*tracepb.ResourceSpans {
|
||||
if len(sdl) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
rsm := make(map[attribute.Distinct]*tracepb.ResourceSpans)
|
||||
|
||||
type key struct {
|
||||
r attribute.Distinct
|
||||
is instrumentation.Scope
|
||||
}
|
||||
ssm := make(map[key]*tracepb.ScopeSpans)
|
||||
|
||||
var resources int
|
||||
for _, sd := range sdl {
|
||||
if sd == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
rKey := sd.Resource().Equivalent()
|
||||
k := key{
|
||||
r: rKey,
|
||||
is: sd.InstrumentationScope(),
|
||||
}
|
||||
scopeSpan, iOk := ssm[k]
|
||||
if !iOk {
|
||||
// Either the resource or instrumentation scope were unknown.
|
||||
scopeSpan = &tracepb.ScopeSpans{
|
||||
Scope: InstrumentationScope(sd.InstrumentationScope()),
|
||||
Spans: []*tracepb.Span{},
|
||||
SchemaUrl: sd.InstrumentationScope().SchemaURL,
|
||||
}
|
||||
}
|
||||
scopeSpan.Spans = append(scopeSpan.Spans, span(sd))
|
||||
ssm[k] = scopeSpan
|
||||
|
||||
rs, rOk := rsm[rKey]
|
||||
if !rOk {
|
||||
resources++
|
||||
// The resource was unknown.
|
||||
rs = &tracepb.ResourceSpans{
|
||||
Resource: Resource(sd.Resource()),
|
||||
ScopeSpans: []*tracepb.ScopeSpans{scopeSpan},
|
||||
SchemaUrl: sd.Resource().SchemaURL(),
|
||||
}
|
||||
rsm[rKey] = rs
|
||||
continue
|
||||
}
|
||||
|
||||
// The resource has been seen before. Check if the instrumentation
|
||||
// library lookup was unknown because if so we need to add it to the
|
||||
// ResourceSpans. Otherwise, the instrumentation library has already
|
||||
// been seen and the append we did above will be included it in the
|
||||
// ScopeSpans reference.
|
||||
if !iOk {
|
||||
rs.ScopeSpans = append(rs.ScopeSpans, scopeSpan)
|
||||
}
|
||||
}
|
||||
|
||||
// Transform the categorized map into a slice
|
||||
rss := make([]*tracepb.ResourceSpans, 0, resources)
|
||||
for _, rs := range rsm {
|
||||
rss = append(rss, rs)
|
||||
}
|
||||
return rss
|
||||
}
|
||||
|
||||
// span transforms a Span into an OTLP span.
|
||||
func span(sd tracesdk.ReadOnlySpan) *tracepb.Span {
|
||||
if sd == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
tid := sd.SpanContext().TraceID()
|
||||
sid := sd.SpanContext().SpanID()
|
||||
|
||||
s := &tracepb.Span{
|
||||
TraceId: tid[:],
|
||||
SpanId: sid[:],
|
||||
TraceState: sd.SpanContext().TraceState().String(),
|
||||
Status: status(sd.Status().Code, sd.Status().Description),
|
||||
StartTimeUnixNano: uint64(max(0, sd.StartTime().UnixNano())), // nolint:gosec // Overflow checked.
|
||||
EndTimeUnixNano: uint64(max(0, sd.EndTime().UnixNano())), // nolint:gosec // Overflow checked.
|
||||
Links: links(sd.Links()),
|
||||
Kind: spanKind(sd.SpanKind()),
|
||||
Name: sd.Name(),
|
||||
Attributes: KeyValues(sd.Attributes()),
|
||||
Events: spanEvents(sd.Events()),
|
||||
DroppedAttributesCount: clampUint32(sd.DroppedAttributes()),
|
||||
DroppedEventsCount: clampUint32(sd.DroppedEvents()),
|
||||
DroppedLinksCount: clampUint32(sd.DroppedLinks()),
|
||||
}
|
||||
|
||||
if psid := sd.Parent().SpanID(); psid.IsValid() {
|
||||
s.ParentSpanId = psid[:]
|
||||
}
|
||||
s.Flags = buildSpanFlagsWith(sd.SpanContext().TraceFlags(), sd.Parent())
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func clampUint32(v int) uint32 {
|
||||
if v < 0 {
|
||||
return 0
|
||||
}
|
||||
if int64(v) > math.MaxUint32 {
|
||||
return math.MaxUint32
|
||||
}
|
||||
return uint32(v) // nolint: gosec // Overflow/Underflow checked.
|
||||
}
|
||||
|
||||
// status transform a span code and message into an OTLP span status.
|
||||
func status(status codes.Code, message string) *tracepb.Status {
|
||||
var c tracepb.Status_StatusCode
|
||||
switch status {
|
||||
case codes.Ok:
|
||||
c = tracepb.Status_STATUS_CODE_OK
|
||||
case codes.Error:
|
||||
c = tracepb.Status_STATUS_CODE_ERROR
|
||||
default:
|
||||
c = tracepb.Status_STATUS_CODE_UNSET
|
||||
}
|
||||
return &tracepb.Status{
|
||||
Code: c,
|
||||
Message: message,
|
||||
}
|
||||
}
|
||||
|
||||
// links transforms span Links to OTLP span links.
|
||||
func links(links []tracesdk.Link) []*tracepb.Span_Link {
|
||||
if len(links) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
sl := make([]*tracepb.Span_Link, 0, len(links))
|
||||
for _, otLink := range links {
|
||||
// This redefinition is necessary to prevent otLink.*ID[:] copies
|
||||
// being reused -- in short we need a new otLink per iteration.
|
||||
|
||||
tid := otLink.SpanContext.TraceID()
|
||||
sid := otLink.SpanContext.SpanID()
|
||||
|
||||
flags := buildSpanFlagsWith(otLink.SpanContext.TraceFlags(), otLink.SpanContext)
|
||||
|
||||
sl = append(sl, &tracepb.Span_Link{
|
||||
TraceId: tid[:],
|
||||
SpanId: sid[:],
|
||||
Attributes: KeyValues(otLink.Attributes),
|
||||
DroppedAttributesCount: clampUint32(otLink.DroppedAttributeCount),
|
||||
Flags: flags,
|
||||
})
|
||||
}
|
||||
return sl
|
||||
}
|
||||
|
||||
func buildSpanFlagsWith(tf trace.TraceFlags, parent trace.SpanContext) uint32 {
|
||||
// Lower 8 bits are the W3C TraceFlags; always indicate that we know whether the parent is remote
|
||||
flags := uint32(tf) | uint32(tracepb.SpanFlags_SPAN_FLAGS_CONTEXT_HAS_IS_REMOTE_MASK)
|
||||
// Set the parent-is-remote bit when applicable
|
||||
if parent.IsRemote() {
|
||||
flags |= uint32(tracepb.SpanFlags_SPAN_FLAGS_CONTEXT_IS_REMOTE_MASK)
|
||||
}
|
||||
|
||||
return flags // nolint:gosec // Flags is a bitmask and can't be negative
|
||||
}
|
||||
|
||||
// spanEvents transforms span Events to an OTLP span events.
|
||||
func spanEvents(es []tracesdk.Event) []*tracepb.Span_Event {
|
||||
if len(es) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
events := make([]*tracepb.Span_Event, len(es))
|
||||
// Transform message events
|
||||
for i := range es {
|
||||
events[i] = &tracepb.Span_Event{
|
||||
Name: es[i].Name,
|
||||
TimeUnixNano: uint64(max(0, es[i].Time.UnixNano())), // nolint:gosec // Overflow checked.
|
||||
Attributes: KeyValues(es[i].Attributes),
|
||||
DroppedAttributesCount: clampUint32(es[i].DroppedAttributeCount),
|
||||
}
|
||||
}
|
||||
return events
|
||||
}
|
||||
|
||||
// spanKind transforms a SpanKind to an OTLP span kind.
|
||||
func spanKind(kind trace.SpanKind) tracepb.Span_SpanKind {
|
||||
switch kind {
|
||||
case trace.SpanKindInternal:
|
||||
return tracepb.Span_SPAN_KIND_INTERNAL
|
||||
case trace.SpanKindClient:
|
||||
return tracepb.Span_SPAN_KIND_CLIENT
|
||||
case trace.SpanKindServer:
|
||||
return tracepb.Span_SPAN_KIND_SERVER
|
||||
case trace.SpanKindProducer:
|
||||
return tracepb.Span_SPAN_KIND_PRODUCER
|
||||
case trace.SpanKindConsumer:
|
||||
return tracepb.Span_SPAN_KIND_CONSUMER
|
||||
default:
|
||||
return tracepb.Span_SPAN_KIND_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+231
@@ -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.
|
||||
Generated
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
# OTLP Trace gRPC Exporter
|
||||
|
||||
[](https://pkg.go.dev/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc)
|
||||
Generated
Vendored
+323
@@ -0,0 +1,323 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package otlptracegrpc // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
coltracepb "go.opentelemetry.io/proto/otlp/collector/trace/v1"
|
||||
tracepb "go.opentelemetry.io/proto/otlp/trace/v1"
|
||||
"google.golang.org/genproto/googleapis/rpc/errdetails"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/counter"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/observ"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/retry"
|
||||
)
|
||||
|
||||
type client struct {
|
||||
endpoint string
|
||||
dialOpts []grpc.DialOption
|
||||
metadata metadata.MD
|
||||
exportTimeout time.Duration
|
||||
requestFunc retry.RequestFunc
|
||||
|
||||
// stopCtx is used as a parent context for all exports. Therefore, when it
|
||||
// is canceled with the stopFunc all exports are canceled.
|
||||
stopCtx context.Context
|
||||
// stopFunc cancels stopCtx, stopping any active exports.
|
||||
stopFunc context.CancelFunc
|
||||
|
||||
// ourConn keeps track of where conn was created: true if created here on
|
||||
// Start, or false if passed with an option. This is important on Shutdown
|
||||
// as the conn should only be closed if created here on start. Otherwise,
|
||||
// it is up to the processes that passed the conn to close it.
|
||||
ourConn bool
|
||||
conn *grpc.ClientConn
|
||||
tscMu sync.RWMutex
|
||||
tsc coltracepb.TraceServiceClient
|
||||
|
||||
instID int64
|
||||
inst *observ.Instrumentation
|
||||
}
|
||||
|
||||
// Compile time check *client implements otlptrace.Client.
|
||||
var _ otlptrace.Client = (*client)(nil)
|
||||
|
||||
// NewClient creates a new gRPC trace client.
|
||||
func NewClient(opts ...Option) otlptrace.Client {
|
||||
return newClient(opts...)
|
||||
}
|
||||
|
||||
func newClient(opts ...Option) *client {
|
||||
cfg := otlpconfig.NewGRPCConfig(asGRPCOptions(opts)...)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background()) //nolint:gosec // cancel called in client shutdown.
|
||||
|
||||
c := &client{
|
||||
endpoint: cfg.Traces.Endpoint,
|
||||
exportTimeout: cfg.Traces.Timeout,
|
||||
requestFunc: cfg.RetryConfig.RequestFunc(retryable),
|
||||
dialOpts: cfg.DialOptions,
|
||||
stopCtx: ctx,
|
||||
stopFunc: cancel,
|
||||
conn: cfg.GRPCConn,
|
||||
instID: counter.NextExporterID(),
|
||||
}
|
||||
|
||||
if len(cfg.Traces.Headers) > 0 {
|
||||
c.metadata = metadata.New(cfg.Traces.Headers)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// Start establishes a gRPC connection to the collector.
|
||||
func (c *client) Start(context.Context) error {
|
||||
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.
|
||||
conn, err := grpc.NewClient(c.endpoint, c.dialOpts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Keep track that we own the lifecycle of this conn and need to close
|
||||
// it on Shutdown.
|
||||
c.ourConn = true
|
||||
c.conn = conn
|
||||
}
|
||||
|
||||
// Initialize the instrumentation if not already done.
|
||||
//
|
||||
// Initialize here instead of NewClient to allow any errors to be passed
|
||||
// back to the caller and so that any setup of the environment variables to
|
||||
// enable instrumentation can be set via code.
|
||||
var err error
|
||||
if c.inst == nil {
|
||||
target := c.conn.CanonicalTarget()
|
||||
c.inst, err = observ.NewInstrumentation(c.instID, target)
|
||||
}
|
||||
|
||||
// The otlptrace.Client interface states this method is called just once,
|
||||
// so no need to check if already started.
|
||||
c.tscMu.Lock()
|
||||
c.tsc = coltracepb.NewTraceServiceClient(c.conn)
|
||||
c.tscMu.Unlock()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var errAlreadyStopped = errors.New("the client is already stopped")
|
||||
|
||||
// Stop shuts down the client.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// This method synchronizes with the UploadTraces method of the client. It
|
||||
// will wait for any active calls to that method to complete unimpeded, or it
|
||||
// will cancel any active calls if ctx expires. If ctx expires, the context
|
||||
// error will be forwarded as the returned error. All client held resources
|
||||
// will still be released in this situation.
|
||||
//
|
||||
// If the client has already stopped, an error will be returned describing
|
||||
// this.
|
||||
func (c *client) Stop(ctx context.Context) error {
|
||||
// Make sure to return context error if the context is done when calling this method.
|
||||
err := ctx.Err()
|
||||
|
||||
// Acquire the c.tscMu lock within the ctx lifetime.
|
||||
acquired := make(chan struct{})
|
||||
go func() {
|
||||
c.tscMu.Lock()
|
||||
close(acquired)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// The Stop timeout is reached. Kill any remaining exports to force
|
||||
// the clear of the lock and save the timeout error to return and
|
||||
// signal the shutdown timed out before cleanly stopping.
|
||||
c.stopFunc()
|
||||
err = ctx.Err()
|
||||
|
||||
// To ensure the client is not left in a dirty state c.tsc needs to be
|
||||
// set to nil. To avoid the race condition when doing this, ensure
|
||||
// that all the exports are killed (initiated by c.stopFunc).
|
||||
<-acquired
|
||||
case <-acquired:
|
||||
}
|
||||
// Hold the tscMu lock for the rest of the function to ensure no new
|
||||
// exports are started.
|
||||
defer c.tscMu.Unlock()
|
||||
|
||||
// The otlptrace.Client interface states this method is called only
|
||||
// once, but there is no guarantee it is called after Start. Ensure the
|
||||
// client is started before doing anything and let the called know if they
|
||||
// made a mistake.
|
||||
if c.tsc == nil {
|
||||
return errAlreadyStopped
|
||||
}
|
||||
|
||||
// Clear c.tsc to signal the client is stopped.
|
||||
c.tsc = nil
|
||||
|
||||
if c.ourConn {
|
||||
closeErr := c.conn.Close()
|
||||
// A context timeout error takes precedence over this error.
|
||||
if err == nil && closeErr != nil {
|
||||
err = closeErr
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
var errShutdown = errors.New("the client is shutdown")
|
||||
|
||||
// UploadTraces sends a batch of spans.
|
||||
//
|
||||
// Retryable errors from the server will be handled according to any
|
||||
// RetryConfig the client was created with.
|
||||
func (c *client) UploadTraces(ctx context.Context, protoSpans []*tracepb.ResourceSpans) (uploadErr error) {
|
||||
// Hold a read lock to ensure a shut down initiated after this starts does
|
||||
// not abandon the export. This read lock acquire has less priority than a
|
||||
// write lock acquire (i.e. Stop), meaning if the client is shutting down
|
||||
// this will come after the shut down.
|
||||
c.tscMu.RLock()
|
||||
defer c.tscMu.RUnlock()
|
||||
|
||||
if c.tsc == nil {
|
||||
return errShutdown
|
||||
}
|
||||
|
||||
ctx, cancel := c.exportContext(ctx)
|
||||
defer cancel()
|
||||
|
||||
var code codes.Code
|
||||
if c.inst != nil {
|
||||
op := c.inst.ExportSpans(ctx, len(protoSpans))
|
||||
defer func() { op.End(uploadErr, code) }()
|
||||
}
|
||||
|
||||
return c.requestFunc(ctx, func(iCtx context.Context) error {
|
||||
resp, err := c.tsc.Export(iCtx, &coltracepb.ExportTraceServiceRequest{
|
||||
ResourceSpans: protoSpans,
|
||||
})
|
||||
if resp != nil && resp.PartialSuccess != nil {
|
||||
msg := resp.PartialSuccess.GetErrorMessage()
|
||||
n := resp.PartialSuccess.GetRejectedSpans()
|
||||
if n != 0 || msg != "" {
|
||||
e := internal.TracePartialSuccessError(n, msg)
|
||||
uploadErr = errors.Join(uploadErr, e)
|
||||
}
|
||||
}
|
||||
// nil is converted to OK.
|
||||
code = status.Code(err)
|
||||
if code == codes.OK {
|
||||
// Success.
|
||||
return uploadErr
|
||||
}
|
||||
return errors.Join(uploadErr, err)
|
||||
})
|
||||
}
|
||||
|
||||
// exportContext returns a copy of parent with an appropriate deadline and
|
||||
// cancellation function.
|
||||
//
|
||||
// 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 called by caller when export is complete.
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// Unify the client stopCtx with the parent.
|
||||
go func() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-c.stopCtx.Done():
|
||||
// Cancel the export as the shutdown has timed out.
|
||||
cancel()
|
||||
}
|
||||
}()
|
||||
|
||||
return ctx, cancel
|
||||
}
|
||||
|
||||
// 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() {
|
||||
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 of the status is RetryInfo
|
||||
// and the its duration to wait for if an explicit throttle time.
|
||||
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
|
||||
}
|
||||
|
||||
// MarshalLog is the marshaling function used by the logging system to represent this Client.
|
||||
func (c *client) MarshalLog() any {
|
||||
return struct {
|
||||
Type string
|
||||
Endpoint string
|
||||
}{
|
||||
Type: "otlptracegrpc",
|
||||
Endpoint: c.endpoint,
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+65
@@ -0,0 +1,65 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/*
|
||||
Package otlptracegrpc provides an OTLP span exporter using gRPC.
|
||||
By default the telemetry is sent to https://localhost:4317.
|
||||
|
||||
Exporter should be created using [New].
|
||||
|
||||
The environment variables described below can be used for configuration.
|
||||
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_TRACES_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_TRACES_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_TRACES_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 the http or https scheme.
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT setting overrides
|
||||
the scheme defined via OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT.
|
||||
OTEL_EXPORTER_OTLP_TRACES_INSECURE takes precedence over OTEL_EXPORTER_OTLP_INSECURE.
|
||||
The configuration can be overridden by [WithInsecure], [WithGRPCConn] options.
|
||||
|
||||
OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_TRACES_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_TRACES_HEADERS takes precedence over OTEL_EXPORTER_OTLP_HEADERS.
|
||||
The configuration can be overridden by [WithHeaders] option.
|
||||
|
||||
OTEL_EXPORTER_OTLP_TIMEOUT, OTEL_EXPORTER_OTLP_TRACES_TIMEOUT (default: "10000") -
|
||||
maximum time in milliseconds the OTLP exporter waits for each batch export.
|
||||
OTEL_EXPORTER_OTLP_TRACES_TIMEOUT takes precedence over OTEL_EXPORTER_OTLP_TIMEOUT.
|
||||
The configuration can be overridden by [WithTimeout] option.
|
||||
|
||||
OTEL_EXPORTER_OTLP_COMPRESSION, OTEL_EXPORTER_OTLP_TRACES_COMPRESSION (default: none) -
|
||||
the gRPC compressor the exporter uses.
|
||||
Supported value: "gzip".
|
||||
OTEL_EXPORTER_OTLP_TRACES_COMPRESSION takes precedence over OTEL_EXPORTER_OTLP_COMPRESSION.
|
||||
The configuration can be overridden by [WithCompressor], [WithGRPCConn] options.
|
||||
|
||||
OTEL_EXPORTER_OTLP_CERTIFICATE, OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE (default: none) -
|
||||
the filepath to the trusted certificate to use when verifying a server's TLS credentials.
|
||||
OTEL_EXPORTER_OTLP_TRACES_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_TRACES_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_TRACES_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_TRACES_CLIENT_KEY (default: none) -
|
||||
the filepath to the client's private key to use in mTLS communication in PEM format.
|
||||
OTEL_EXPORTER_OTLP_TRACES_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 otlptracegrpc // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
||||
Generated
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package otlptracegrpc // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
||||
)
|
||||
|
||||
// New constructs a new Exporter and starts it.
|
||||
func New(ctx context.Context, opts ...Option) (*otlptrace.Exporter, error) {
|
||||
return otlptrace.New(ctx, NewClient(opts...))
|
||||
}
|
||||
|
||||
// NewUnstarted constructs a new Exporter and does not start it.
|
||||
func NewUnstarted(opts ...Option) *otlptrace.Exporter {
|
||||
return otlptrace.NewUnstarted(NewClient(opts...))
|
||||
}
|
||||
Generated
Vendored
+31
@@ -0,0 +1,31 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/counter/counter.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package counter provides a simple counter for generating unique IDs.
|
||||
//
|
||||
// This package is used to generate unique IDs while allowing testing packages
|
||||
// to reset the counter.
|
||||
package counter // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/counter"
|
||||
|
||||
import "sync/atomic"
|
||||
|
||||
// exporterN is a global 0-based count of the number of exporters created.
|
||||
var exporterN atomic.Int64
|
||||
|
||||
// NextExporterID returns the next unique ID for an exporter.
|
||||
func NextExporterID() int64 {
|
||||
const inc = 1
|
||||
return exporterN.Add(inc) - inc
|
||||
}
|
||||
|
||||
// SetExporterID 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 SetExporterID(v int64) int64 {
|
||||
return exporterN.Swap(v)
|
||||
}
|
||||
Generated
Vendored
+217
@@ -0,0 +1,217 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/otlp/envconfig/envconfig.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package envconfig provides functionality to parse configuration from
|
||||
// environment variables.
|
||||
package envconfig // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig"
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"go.opentelemetry.io/otel/internal/global"
|
||||
)
|
||||
|
||||
// ConfigFn is the generic function used to set a config.
|
||||
type ConfigFn func(*EnvOptionsReader)
|
||||
|
||||
// EnvOptionsReader reads the required environment variables.
|
||||
type EnvOptionsReader struct {
|
||||
GetEnv func(string) string
|
||||
ReadFile func(string) ([]byte, error)
|
||||
Namespace string
|
||||
}
|
||||
|
||||
// Apply runs every ConfigFn.
|
||||
func (e *EnvOptionsReader) Apply(opts ...ConfigFn) {
|
||||
for _, o := range opts {
|
||||
o(e)
|
||||
}
|
||||
}
|
||||
|
||||
// GetEnvValue gets an OTLP environment variable value of the specified key
|
||||
// using the GetEnv function.
|
||||
// This function prepends the OTLP specified namespace to all key lookups.
|
||||
func (e *EnvOptionsReader) GetEnvValue(key string) (string, bool) {
|
||||
v := strings.TrimSpace(e.GetEnv(keyWithNamespace(e.Namespace, key)))
|
||||
return v, v != ""
|
||||
}
|
||||
|
||||
// WithString retrieves the specified config and passes it to ConfigFn as a string.
|
||||
func WithString(n string, fn func(string)) func(e *EnvOptionsReader) {
|
||||
return func(e *EnvOptionsReader) {
|
||||
if v, ok := e.GetEnvValue(n); ok {
|
||||
fn(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithBool returns a ConfigFn that reads the environment variable n and if it exists passes its parsed bool value to fn.
|
||||
func WithBool(n string, fn func(bool)) ConfigFn {
|
||||
return func(e *EnvOptionsReader) {
|
||||
if v, ok := e.GetEnvValue(n); ok {
|
||||
b := strings.ToLower(v) == "true"
|
||||
fn(b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithDuration retrieves the specified config and passes it to ConfigFn as a duration.
|
||||
func WithDuration(n string, fn func(time.Duration)) func(e *EnvOptionsReader) {
|
||||
return func(e *EnvOptionsReader) {
|
||||
if v, ok := e.GetEnvValue(n); ok {
|
||||
d, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
global.Error(err, "parse duration", "input", v)
|
||||
return
|
||||
}
|
||||
fn(time.Duration(d) * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithHeaders retrieves the specified config and passes it to ConfigFn as a map of HTTP headers.
|
||||
func WithHeaders(n string, fn func(map[string]string)) func(e *EnvOptionsReader) {
|
||||
return func(e *EnvOptionsReader) {
|
||||
if v, ok := e.GetEnvValue(n); ok {
|
||||
fn(stringToHeader(v))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithURL retrieves the specified config and passes it to ConfigFn as a net/url.URL.
|
||||
func WithURL(n string, fn func(*url.URL)) func(e *EnvOptionsReader) {
|
||||
return func(e *EnvOptionsReader) {
|
||||
if v, ok := e.GetEnvValue(n); ok {
|
||||
u, err := url.Parse(v)
|
||||
if err != nil {
|
||||
global.Error(err, "parse url", "input", v)
|
||||
return
|
||||
}
|
||||
fn(u)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithCertPool returns a ConfigFn that reads the environment variable n as a filepath to a TLS certificate pool. If it exists, it is parsed as a crypto/x509.CertPool and it is passed to fn.
|
||||
func WithCertPool(n string, fn func(*x509.CertPool)) ConfigFn {
|
||||
return func(e *EnvOptionsReader) {
|
||||
if v, ok := e.GetEnvValue(n); ok {
|
||||
b, err := e.ReadFile(v)
|
||||
if err != nil {
|
||||
global.Error(err, "read tls ca cert file", "file", v)
|
||||
return
|
||||
}
|
||||
c, err := createCertPool(b)
|
||||
if err != nil {
|
||||
global.Error(err, "create tls cert pool")
|
||||
return
|
||||
}
|
||||
fn(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithClientCert returns a ConfigFn that reads the environment variable nc and nk as filepaths to a client certificate and key pair. If they exists, they are parsed as a crypto/tls.Certificate and it is passed to fn.
|
||||
func WithClientCert(nc, nk string, fn func(tls.Certificate)) ConfigFn {
|
||||
return func(e *EnvOptionsReader) {
|
||||
vc, okc := e.GetEnvValue(nc)
|
||||
vk, okk := e.GetEnvValue(nk)
|
||||
if !okc || !okk {
|
||||
return
|
||||
}
|
||||
cert, err := e.ReadFile(vc)
|
||||
if err != nil {
|
||||
global.Error(err, "read tls client cert", "file", vc)
|
||||
return
|
||||
}
|
||||
key, err := e.ReadFile(vk)
|
||||
if err != nil {
|
||||
global.Error(err, "read tls client key", "file", vk)
|
||||
return
|
||||
}
|
||||
crt, err := tls.X509KeyPair(cert, key)
|
||||
if err != nil {
|
||||
global.Error(err, "create tls client key pair")
|
||||
return
|
||||
}
|
||||
fn(crt)
|
||||
}
|
||||
}
|
||||
|
||||
func keyWithNamespace(ns, key string) string {
|
||||
if ns == "" {
|
||||
return key
|
||||
}
|
||||
return fmt.Sprintf("%s_%s", ns, key)
|
||||
}
|
||||
|
||||
func stringToHeader(value string) map[string]string {
|
||||
headersPairs := strings.Split(value, ",")
|
||||
headers := make(map[string]string)
|
||||
|
||||
for _, header := range headersPairs {
|
||||
n, v, found := strings.Cut(header, "=")
|
||||
if !found {
|
||||
global.Error(errors.New("missing '="), "parse headers", "input", header)
|
||||
continue
|
||||
}
|
||||
|
||||
trimmedName := strings.TrimSpace(n)
|
||||
|
||||
// Validate the key.
|
||||
if !isValidHeaderKey(trimmedName) {
|
||||
global.Error(errors.New("invalid header key"), "parse headers", "key", trimmedName)
|
||||
continue
|
||||
}
|
||||
|
||||
// Only decode the value.
|
||||
value, err := url.PathUnescape(v)
|
||||
if err != nil {
|
||||
global.Error(err, "escape header value", "value", v)
|
||||
continue
|
||||
}
|
||||
trimmedValue := strings.TrimSpace(value)
|
||||
|
||||
headers[trimmedName] = trimmedValue
|
||||
}
|
||||
|
||||
return headers
|
||||
}
|
||||
|
||||
func createCertPool(certBytes []byte) (*x509.CertPool, error) {
|
||||
cp := x509.NewCertPool()
|
||||
if ok := cp.AppendCertsFromPEM(certBytes); !ok {
|
||||
return nil, errors.New("failed to append certificate to the cert pool")
|
||||
}
|
||||
return cp, nil
|
||||
}
|
||||
|
||||
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 == '~')
|
||||
}
|
||||
Generated
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package internal provides internal functionally for the otlptracegrpc package.
|
||||
package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal"
|
||||
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/partialsuccess.go.tmpl "--data={}" --out=partialsuccess.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/partialsuccess_test.go.tmpl "--data={}" --out=partialsuccess_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/envconfig/envconfig.go.tmpl "--data={}" --out=envconfig/envconfig.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/envconfig/envconfig_test.go.tmpl "--data={}" --out=envconfig/envconfig_test.go
|
||||
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlptrace/otlpconfig/envconfig.go.tmpl "--data={\"envconfigImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig\"}" --out=otlpconfig/envconfig.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlptrace/otlpconfig/options.go.tmpl "--data={\"retryImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/retry\"}" --out=otlpconfig/options.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlptrace/otlpconfig/options_test.go.tmpl "--data={\"envconfigImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig\"}" --out=otlpconfig/options_test.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlptrace/otlpconfig/optiontypes.go.tmpl "--data={}" --out=otlpconfig/optiontypes.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlptrace/otlpconfig/tls.go.tmpl "--data={}" --out=otlpconfig/tls.go
|
||||
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlptrace/otlptracetest/client.go.tmpl "--data={}" --out=otlptracetest/client.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlptrace/otlptracetest/collector.go.tmpl "--data={}" --out=otlptracetest/collector.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlptrace/otlptracetest/data.go.tmpl "--data={}" --out=otlptracetest/data.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlptrace/otlptracetest/otlptest.go.tmpl "--data={}" --out=otlptracetest/otlptest.go
|
||||
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/x/x.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc\" }" --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/observ/target.go.tmpl "--data={ \"pkg\": \"observ\", \"pkg_path\": \"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/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/counter/counter.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/counter\" }" --out=counter/counter.go
|
||||
//go:generate gotmpl --body=../../../../../internal/shared/counter/counter_test.go.tmpl "--data={}" --out=counter/counter_test.go
|
||||
Server/vendor/go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/observ/doc.go
Generated
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package observ provides experimental observability instrumentation for the
|
||||
// otlptracegrpc exporter.
|
||||
package observ // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/observ"
|
||||
Generated
Vendored
+350
@@ -0,0 +1,350 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package observ // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/observ"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/x"
|
||||
"go.opentelemetry.io/otel/internal/global"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.40.0/otelconv"
|
||||
)
|
||||
|
||||
const (
|
||||
// ScopeName is the unique name of the meter used for instrumentation.
|
||||
ScopeName = "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/observ"
|
||||
|
||||
// SchemaURL is the schema URL of the metrics produced by this
|
||||
// instrumentation.
|
||||
SchemaURL = semconv.SchemaURL
|
||||
|
||||
// Version is the current version of this instrumentation.
|
||||
//
|
||||
// This matches the version of the exporter.
|
||||
Version = internal.Version
|
||||
)
|
||||
|
||||
var (
|
||||
measureAttrsPool = &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
|
||||
},
|
||||
}
|
||||
|
||||
addOptPool = &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) {
|
||||
*s = (*s)[:0] // Reset.
|
||||
p.Put(s)
|
||||
}
|
||||
|
||||
// ComponentName returns the component name for the exporter with the
|
||||
// provided ID.
|
||||
func ComponentName(id int64) string {
|
||||
t := semconv.OTelComponentTypeOtlpGRPCSpanExporter.Value.AsString()
|
||||
return fmt.Sprintf("%s/%d", t, id)
|
||||
}
|
||||
|
||||
// Instrumentation is experimental instrumentation for the exporter.
|
||||
type Instrumentation struct {
|
||||
inflightSpans metric.Int64UpDownCounter
|
||||
exportedSpans metric.Int64Counter
|
||||
opDuration metric.Float64Histogram
|
||||
|
||||
attrs []attribute.KeyValue
|
||||
addOpt metric.AddOption
|
||||
recOpt metric.RecordOption
|
||||
}
|
||||
|
||||
// NewInstrumentation returns instrumentation for an OTLP over gPRC trace
|
||||
// exporter with the provided ID using the global MeterProvider.
|
||||
//
|
||||
// The id should be the unique exporter instance ID. It is used
|
||||
// to set the "component.name" attribute.
|
||||
//
|
||||
// The target is the endpoint the exporter is exporting to.
|
||||
//
|
||||
// If the experimental observability is disabled, nil is returned.
|
||||
func NewInstrumentation(id int64, target string) (*Instrumentation, error) {
|
||||
if !x.Observability.Enabled() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
attrs := BaseAttrs(id, target)
|
||||
i := &Instrumentation{
|
||||
attrs: attrs,
|
||||
addOpt: metric.WithAttributeSet(attribute.NewSet(attrs...)),
|
||||
|
||||
// Do not modify attrs (NewSet sorts in-place), make a new slice.
|
||||
recOpt: metric.WithAttributeSet(attribute.NewSet(append(
|
||||
// Default to OK status code.
|
||||
[]attribute.KeyValue{
|
||||
semconv.RPCResponseStatusCode(codes.OK.String()),
|
||||
},
|
||||
attrs...,
|
||||
)...)),
|
||||
}
|
||||
|
||||
mp := otel.GetMeterProvider()
|
||||
m := mp.Meter(
|
||||
ScopeName,
|
||||
metric.WithInstrumentationVersion(Version),
|
||||
metric.WithSchemaURL(SchemaURL),
|
||||
)
|
||||
|
||||
var err error
|
||||
|
||||
inflightSpans, e := otelconv.NewSDKExporterSpanInflight(m)
|
||||
if e != nil {
|
||||
e = fmt.Errorf("failed to create span inflight metric: %w", e)
|
||||
err = errors.Join(err, e)
|
||||
}
|
||||
i.inflightSpans = inflightSpans.Inst()
|
||||
|
||||
exportedSpans, e := otelconv.NewSDKExporterSpanExported(m)
|
||||
if e != nil {
|
||||
e = fmt.Errorf("failed to create span exported metric: %w", e)
|
||||
err = errors.Join(err, e)
|
||||
}
|
||||
i.exportedSpans = exportedSpans.Inst()
|
||||
|
||||
opDuration, e := otelconv.NewSDKExporterOperationDuration(m)
|
||||
if e != nil {
|
||||
e = fmt.Errorf("failed to create operation duration metric: %w", e)
|
||||
err = errors.Join(err, e)
|
||||
}
|
||||
i.opDuration = opDuration.Inst()
|
||||
|
||||
return i, err
|
||||
}
|
||||
|
||||
// BaseAttrs returns the base attributes for the exporter with the provided ID
|
||||
// and target.
|
||||
//
|
||||
// The id should be the unique exporter instance ID. It is used
|
||||
// to set the "component.name" attribute.
|
||||
//
|
||||
// The target is the gRPC target the exporter is exporting to. It is expected
|
||||
// to be the output of the Client's CanonicalTarget method.
|
||||
func BaseAttrs(id int64, target string) []attribute.KeyValue {
|
||||
host, port, err := ParseCanonicalTarget(target)
|
||||
if err != nil || (host == "" && port < 0) {
|
||||
if err != nil {
|
||||
global.Debug("failed to parse target", "target", target, "error", err)
|
||||
}
|
||||
return []attribute.KeyValue{
|
||||
semconv.OTelComponentName(ComponentName(id)),
|
||||
semconv.OTelComponentTypeOtlpGRPCSpanExporter,
|
||||
}
|
||||
}
|
||||
|
||||
// Do not use append so the slice is exactly allocated.
|
||||
|
||||
if port < 0 {
|
||||
return []attribute.KeyValue{
|
||||
semconv.OTelComponentName(ComponentName(id)),
|
||||
semconv.OTelComponentTypeOtlpGRPCSpanExporter,
|
||||
semconv.ServerAddress(host),
|
||||
}
|
||||
}
|
||||
|
||||
if host == "" {
|
||||
return []attribute.KeyValue{
|
||||
semconv.OTelComponentName(ComponentName(id)),
|
||||
semconv.OTelComponentTypeOtlpGRPCSpanExporter,
|
||||
semconv.ServerPort(port),
|
||||
}
|
||||
}
|
||||
|
||||
return []attribute.KeyValue{
|
||||
semconv.OTelComponentName(ComponentName(id)),
|
||||
semconv.OTelComponentTypeOtlpGRPCSpanExporter,
|
||||
semconv.ServerAddress(host),
|
||||
semconv.ServerPort(port),
|
||||
}
|
||||
}
|
||||
|
||||
// ExportSpans instruments the ExportSpans method of the exporter. It returns
|
||||
// an [ExportOp] that must have its [ExportOp.End] method called when the
|
||||
// ExportSpans method returns.
|
||||
func (i *Instrumentation) ExportSpans(ctx context.Context, nSpans int) ExportOp {
|
||||
start := time.Now()
|
||||
|
||||
if i.inflightSpans.Enabled(ctx) {
|
||||
addOpt := get[metric.AddOption](addOptPool)
|
||||
defer put(addOptPool, addOpt)
|
||||
*addOpt = append(*addOpt, i.addOpt)
|
||||
i.inflightSpans.Add(ctx, int64(nSpans), *addOpt...)
|
||||
}
|
||||
|
||||
return ExportOp{
|
||||
ctx: ctx,
|
||||
start: start,
|
||||
nSpans: int64(nSpans),
|
||||
inst: i,
|
||||
}
|
||||
}
|
||||
|
||||
// ExportOp tracks the operation being observed by [Instrumentation.ExportSpans].
|
||||
type ExportOp struct {
|
||||
ctx context.Context
|
||||
start time.Time
|
||||
nSpans int64
|
||||
|
||||
inst *Instrumentation
|
||||
}
|
||||
|
||||
// End completes the observation of the operation being observed by a call to
|
||||
// [Instrumentation.ExportSpans].
|
||||
//
|
||||
// Any error that is encountered is provided as err.
|
||||
//
|
||||
// If err is not nil, all spans will be recorded as failures unless error is of
|
||||
// type [internal.PartialSuccess]. In the case of a PartialSuccess, the number
|
||||
// of successfully exported spans will be determined by inspecting the
|
||||
// RejectedItems field of the PartialSuccess.
|
||||
func (e ExportOp) End(err error, code codes.Code) {
|
||||
addOpt := get[metric.AddOption](addOptPool)
|
||||
defer put(addOptPool, addOpt)
|
||||
*addOpt = append(*addOpt, e.inst.addOpt)
|
||||
|
||||
if e.inst.inflightSpans.Enabled(e.ctx) {
|
||||
e.inst.inflightSpans.Add(e.ctx, -e.nSpans, *addOpt...)
|
||||
}
|
||||
|
||||
success := successful(e.nSpans, err)
|
||||
// Record successfully exported spans, even if the value is 0 which are
|
||||
// meaningful to distribution aggregations.
|
||||
if e.inst.exportedSpans.Enabled(e.ctx) {
|
||||
e.inst.exportedSpans.Add(e.ctx, success, *addOpt...)
|
||||
}
|
||||
|
||||
if err != nil && e.inst.exportedSpans.Enabled(e.ctx) {
|
||||
attrs := get[attribute.KeyValue](measureAttrsPool)
|
||||
defer put(measureAttrsPool, attrs)
|
||||
*attrs = append(*attrs, e.inst.attrs...)
|
||||
*attrs = append(*attrs, semconv.ErrorType(err))
|
||||
|
||||
// Do not inefficiently make a copy of attrs by using
|
||||
// WithAttributes instead of WithAttributeSet.
|
||||
o := metric.WithAttributeSet(attribute.NewSet(*attrs...))
|
||||
// Reset addOpt with new attribute set.
|
||||
*addOpt = append((*addOpt)[:0], o)
|
||||
|
||||
e.inst.exportedSpans.Add(e.ctx, e.nSpans-success, *addOpt...)
|
||||
}
|
||||
|
||||
if e.inst.opDuration.Enabled(e.ctx) {
|
||||
recOpt := get[metric.RecordOption](recordOptPool)
|
||||
defer put(recordOptPool, recOpt)
|
||||
*recOpt = append(*recOpt, e.inst.recordOption(err, code))
|
||||
|
||||
d := time.Since(e.start).Seconds()
|
||||
e.inst.opDuration.Record(e.ctx, d, *recOpt...)
|
||||
}
|
||||
}
|
||||
|
||||
// recordOption returns a RecordOption with attributes representing the
|
||||
// outcome of the operation being recorded.
|
||||
//
|
||||
// If err is nil and code is codes.OK, the default recOpt of the
|
||||
// Instrumentation is returned.
|
||||
//
|
||||
// If err is not nil or code is not codes.OK, a new RecordOption is returned
|
||||
// with the base attributes of the Instrumentation plus the rpc.grpc.status_code
|
||||
// attribute set to the provided code, and if err is not nil, the error.type
|
||||
// attribute set to the type of the error.
|
||||
func (i *Instrumentation) recordOption(err error, code codes.Code) metric.RecordOption {
|
||||
if err == nil && code == codes.OK {
|
||||
return i.recOpt
|
||||
}
|
||||
|
||||
attrs := get[attribute.KeyValue](measureAttrsPool)
|
||||
defer put(measureAttrsPool, attrs)
|
||||
*attrs = append(*attrs, i.attrs...)
|
||||
|
||||
*attrs = append(*attrs, semconv.RPCResponseStatusCode(code.String()))
|
||||
if err != nil {
|
||||
*attrs = append(*attrs, semconv.ErrorType(err))
|
||||
}
|
||||
|
||||
// Do not inefficiently make a copy of attrs by using WithAttributes
|
||||
// instead of WithAttributeSet.
|
||||
return metric.WithAttributeSet(attribute.NewSet(*attrs...))
|
||||
}
|
||||
|
||||
// successful returns the number of successfully exported spans out of the n
|
||||
// that were exported based on the provided error.
|
||||
//
|
||||
// If err is nil, n is returned. All spans were successfully exported.
|
||||
//
|
||||
// If err is not nil and not an [internal.PartialSuccess] error, 0 is returned.
|
||||
// It is assumed all spans failed to be exported.
|
||||
//
|
||||
// If err is an [internal.PartialSuccess] error, the number of successfully
|
||||
// exported spans 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 spans successfully exported.
|
||||
}
|
||||
// Split rejection calculation so successful is inlinable.
|
||||
return n - rejected(n, err)
|
||||
}
|
||||
|
||||
var errPartialPool = &sync.Pool{
|
||||
New: func() any { return new(internal.PartialSuccess) },
|
||||
}
|
||||
|
||||
// rejected returns how many out of the n spans exporter were rejected based on
|
||||
// the provided non-nil err.
|
||||
func rejected(n int64, err error) int64 {
|
||||
ps := errPartialPool.Get().(*internal.PartialSuccess)
|
||||
defer errPartialPool.Put(ps)
|
||||
// Check for partial success.
|
||||
if errors.As(err, ps) {
|
||||
// Bound RejectedItems to [0, n]. This should not be needed,
|
||||
// but be defensive as this is from an external source.
|
||||
return min(max(ps.RejectedItems, 0), n)
|
||||
}
|
||||
return n // All spans rejected.
|
||||
}
|
||||
Generated
Vendored
+143
@@ -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/otlptrace/otlptracegrpc/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
|
||||
// "<scheme>://[authority]/<endpoint>". 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()
|
||||
}
|
||||
Generated
Vendored
+150
@@ -0,0 +1,150 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/otlp/otlptrace/otlpconfig/envconfig.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package otlpconfig // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig"
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig"
|
||||
)
|
||||
|
||||
// DefaultEnvOptionsReader is the default environments reader.
|
||||
var DefaultEnvOptionsReader = envconfig.EnvOptionsReader{
|
||||
GetEnv: os.Getenv,
|
||||
ReadFile: os.ReadFile,
|
||||
Namespace: "OTEL_EXPORTER_OTLP",
|
||||
}
|
||||
|
||||
// ApplyGRPCEnvConfigs applies the env configurations for gRPC.
|
||||
func ApplyGRPCEnvConfigs(cfg Config) Config {
|
||||
opts := getOptionsFromEnv()
|
||||
for _, opt := range opts {
|
||||
cfg = opt.ApplyGRPCOption(cfg)
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
// ApplyHTTPEnvConfigs applies the env configurations for HTTP.
|
||||
func ApplyHTTPEnvConfigs(cfg Config) Config {
|
||||
opts := getOptionsFromEnv()
|
||||
for _, opt := range opts {
|
||||
cfg = opt.ApplyHTTPOption(cfg)
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func getOptionsFromEnv() []GenericOption {
|
||||
opts := []GenericOption{}
|
||||
|
||||
tlsConf := &tls.Config{}
|
||||
DefaultEnvOptionsReader.Apply(
|
||||
envconfig.WithURL("ENDPOINT", func(u *url.URL) {
|
||||
opts = append(opts, withEndpointScheme(u))
|
||||
opts = append(opts, newSplitOption(func(cfg Config) Config {
|
||||
cfg.Traces.Endpoint = u.Host
|
||||
// For OTLP/HTTP endpoint URLs without a per-signal
|
||||
// configuration, the passed endpoint is used as a base URL
|
||||
// and the signals are sent to these paths relative to that.
|
||||
cfg.Traces.URLPath = path.Join(u.Path, DefaultTracesPath)
|
||||
return cfg
|
||||
}, withEndpointForGRPC(u)))
|
||||
}),
|
||||
envconfig.WithURL("TRACES_ENDPOINT", func(u *url.URL) {
|
||||
opts = append(opts, withEndpointScheme(u))
|
||||
opts = append(opts, newSplitOption(func(cfg Config) Config {
|
||||
cfg.Traces.Endpoint = u.Host
|
||||
// For endpoint URLs for OTLP/HTTP per-signal variables, the
|
||||
// URL MUST be used as-is without any modification. The only
|
||||
// exception is that if an URL contains no path part, the root
|
||||
// path / MUST be used.
|
||||
path := u.Path
|
||||
if path == "" {
|
||||
path = "/"
|
||||
}
|
||||
cfg.Traces.URLPath = path
|
||||
return cfg
|
||||
}, withEndpointForGRPC(u)))
|
||||
}),
|
||||
envconfig.WithCertPool("CERTIFICATE", func(p *x509.CertPool) { tlsConf.RootCAs = p }),
|
||||
envconfig.WithCertPool("TRACES_CERTIFICATE", func(p *x509.CertPool) { tlsConf.RootCAs = p }),
|
||||
envconfig.WithClientCert(
|
||||
"CLIENT_CERTIFICATE",
|
||||
"CLIENT_KEY",
|
||||
func(c tls.Certificate) { tlsConf.Certificates = []tls.Certificate{c} },
|
||||
),
|
||||
envconfig.WithClientCert(
|
||||
"TRACES_CLIENT_CERTIFICATE",
|
||||
"TRACES_CLIENT_KEY",
|
||||
func(c tls.Certificate) { tlsConf.Certificates = []tls.Certificate{c} },
|
||||
),
|
||||
withTLSConfig(tlsConf, func(c *tls.Config) { opts = append(opts, WithTLSClientConfig(c)) }),
|
||||
envconfig.WithBool("INSECURE", func(b bool) { opts = append(opts, withInsecure(b)) }),
|
||||
envconfig.WithBool("TRACES_INSECURE", func(b bool) { opts = append(opts, withInsecure(b)) }),
|
||||
envconfig.WithHeaders("HEADERS", func(h map[string]string) { opts = append(opts, WithHeaders(h)) }),
|
||||
envconfig.WithHeaders("TRACES_HEADERS", func(h map[string]string) { opts = append(opts, WithHeaders(h)) }),
|
||||
WithEnvCompression("COMPRESSION", func(c Compression) { opts = append(opts, WithCompression(c)) }),
|
||||
WithEnvCompression("TRACES_COMPRESSION", func(c Compression) { opts = append(opts, WithCompression(c)) }),
|
||||
envconfig.WithDuration("TIMEOUT", func(d time.Duration) { opts = append(opts, WithTimeout(d)) }),
|
||||
envconfig.WithDuration("TRACES_TIMEOUT", func(d time.Duration) { opts = append(opts, WithTimeout(d)) }),
|
||||
)
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
func withEndpointScheme(u *url.URL) GenericOption {
|
||||
switch strings.ToLower(u.Scheme) {
|
||||
case "http", "unix":
|
||||
return WithInsecure()
|
||||
default:
|
||||
return WithSecure()
|
||||
}
|
||||
}
|
||||
|
||||
func withEndpointForGRPC(u *url.URL) func(cfg Config) Config {
|
||||
return func(cfg Config) Config {
|
||||
// For OTLP/gRPC endpoints, this is the target to which the
|
||||
// exporter is going to send telemetry.
|
||||
cfg.Traces.Endpoint = path.Join(u.Host, u.Path)
|
||||
return cfg
|
||||
}
|
||||
}
|
||||
|
||||
// WithEnvCompression retrieves the specified config and passes it to ConfigFn as a Compression.
|
||||
func WithEnvCompression(n string, fn func(Compression)) func(e *envconfig.EnvOptionsReader) {
|
||||
return func(e *envconfig.EnvOptionsReader) {
|
||||
if v, ok := e.GetEnvValue(n); ok {
|
||||
cp := NoCompression
|
||||
if v == "gzip" {
|
||||
cp = GzipCompression
|
||||
}
|
||||
|
||||
fn(cp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// revive:disable-next-line:flag-parameter
|
||||
func withInsecure(b bool) GenericOption {
|
||||
if b {
|
||||
return WithInsecure()
|
||||
}
|
||||
return WithSecure()
|
||||
}
|
||||
|
||||
func withTLSConfig(c *tls.Config, fn func(*tls.Config)) func(e *envconfig.EnvOptionsReader) {
|
||||
return func(e *envconfig.EnvOptionsReader) {
|
||||
if c.RootCAs != nil || len(c.Certificates) > 0 {
|
||||
fn(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+360
@@ -0,0 +1,360 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/otlp/otlptrace/otlpconfig/options.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package otlpconfig provides configuration for the otlptrace exporters.
|
||||
package otlpconfig // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig"
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/backoff"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/encoding/gzip"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/retry"
|
||||
"go.opentelemetry.io/otel/internal/global"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultTracesPath is a default URL path for endpoint that
|
||||
// receives spans.
|
||||
DefaultTracesPath string = "/v1/traces"
|
||||
// DefaultTimeout is a default max waiting time for the backend to process
|
||||
// each span batch.
|
||||
DefaultTimeout time.Duration = 10 * time.Second
|
||||
)
|
||||
|
||||
type (
|
||||
// HTTPTransportProxyFunc is a function that resolves which URL to use as proxy for a given request.
|
||||
// This type is compatible with `http.Transport.Proxy` and can be used to set a custom proxy function to the OTLP HTTP client.
|
||||
HTTPTransportProxyFunc func(*http.Request) (*url.URL, error)
|
||||
|
||||
SignalConfig struct {
|
||||
Endpoint string
|
||||
Insecure bool
|
||||
TLSCfg *tls.Config
|
||||
Headers map[string]string
|
||||
Compression Compression
|
||||
Timeout time.Duration
|
||||
URLPath string
|
||||
|
||||
// gRPC configurations
|
||||
GRPCCredentials credentials.TransportCredentials
|
||||
|
||||
// HTTP configurations
|
||||
Proxy HTTPTransportProxyFunc
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
Config struct {
|
||||
// Signal specific configurations
|
||||
Traces SignalConfig
|
||||
|
||||
RetryConfig retry.Config
|
||||
|
||||
// gRPC configurations
|
||||
ReconnectionPeriod time.Duration
|
||||
ServiceConfig string
|
||||
DialOptions []grpc.DialOption
|
||||
GRPCConn *grpc.ClientConn
|
||||
}
|
||||
)
|
||||
|
||||
// NewHTTPConfig returns a new Config with all settings applied from opts and
|
||||
// any unset setting using the default HTTP config values.
|
||||
func NewHTTPConfig(opts ...HTTPOption) Config {
|
||||
cfg := Config{
|
||||
Traces: SignalConfig{
|
||||
Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorHTTPPort),
|
||||
URLPath: DefaultTracesPath,
|
||||
Compression: NoCompression,
|
||||
Timeout: DefaultTimeout,
|
||||
},
|
||||
RetryConfig: retry.DefaultConfig,
|
||||
}
|
||||
cfg = ApplyHTTPEnvConfigs(cfg)
|
||||
for _, opt := range opts {
|
||||
cfg = opt.ApplyHTTPOption(cfg)
|
||||
}
|
||||
cfg.Traces.URLPath = cleanPath(cfg.Traces.URLPath, DefaultTracesPath)
|
||||
return cfg
|
||||
}
|
||||
|
||||
// cleanPath returns a path with all spaces trimmed. If urlPath is empty,
|
||||
// defaultPath is returned instead.
|
||||
func cleanPath(urlPath string, defaultPath string) string {
|
||||
tmp := strings.TrimSpace(urlPath)
|
||||
if tmp == "" || tmp == "." {
|
||||
return defaultPath
|
||||
}
|
||||
if !path.IsAbs(tmp) {
|
||||
tmp = "/" + tmp
|
||||
}
|
||||
return tmp
|
||||
}
|
||||
|
||||
// NewGRPCConfig returns a new Config with all settings applied from opts and
|
||||
// any unset setting using the default gRPC config values.
|
||||
func NewGRPCConfig(opts ...GRPCOption) Config {
|
||||
userAgent := "OTel OTLP Exporter Go/" + otlptrace.Version()
|
||||
cfg := Config{
|
||||
Traces: SignalConfig{
|
||||
Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorGRPCPort),
|
||||
URLPath: DefaultTracesPath,
|
||||
Compression: NoCompression,
|
||||
Timeout: DefaultTimeout,
|
||||
},
|
||||
RetryConfig: retry.DefaultConfig,
|
||||
DialOptions: []grpc.DialOption{grpc.WithUserAgent(userAgent)},
|
||||
}
|
||||
cfg = ApplyGRPCEnvConfigs(cfg)
|
||||
for _, opt := range opts {
|
||||
cfg = opt.ApplyGRPCOption(cfg)
|
||||
}
|
||||
|
||||
if cfg.ServiceConfig != "" {
|
||||
cfg.DialOptions = append(cfg.DialOptions, grpc.WithDefaultServiceConfig(cfg.ServiceConfig))
|
||||
}
|
||||
// Prioritize GRPCCredentials over Insecure (passing both is an error).
|
||||
if cfg.Traces.GRPCCredentials != nil {
|
||||
cfg.DialOptions = append(cfg.DialOptions, grpc.WithTransportCredentials(cfg.Traces.GRPCCredentials))
|
||||
} else if cfg.Traces.Insecure {
|
||||
cfg.DialOptions = append(cfg.DialOptions, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
} else {
|
||||
// Default to using the host's root CA.
|
||||
creds := credentials.NewTLS(nil)
|
||||
cfg.Traces.GRPCCredentials = creds
|
||||
cfg.DialOptions = append(cfg.DialOptions, grpc.WithTransportCredentials(creds))
|
||||
}
|
||||
if cfg.Traces.Compression == GzipCompression {
|
||||
cfg.DialOptions = append(cfg.DialOptions, grpc.WithDefaultCallOptions(grpc.UseCompressor(gzip.Name)))
|
||||
}
|
||||
if cfg.ReconnectionPeriod != 0 {
|
||||
p := grpc.ConnectParams{
|
||||
Backoff: backoff.DefaultConfig,
|
||||
MinConnectTimeout: cfg.ReconnectionPeriod,
|
||||
}
|
||||
cfg.DialOptions = append(cfg.DialOptions, grpc.WithConnectParams(p))
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
type (
|
||||
// GenericOption applies an option to the HTTP or gRPC driver.
|
||||
GenericOption interface {
|
||||
ApplyHTTPOption(Config) Config
|
||||
ApplyGRPCOption(Config) Config
|
||||
|
||||
// A private method to prevent users implementing the
|
||||
// interface and so future additions to it will not
|
||||
// violate compatibility.
|
||||
private()
|
||||
}
|
||||
|
||||
// HTTPOption applies an option to the HTTP driver.
|
||||
HTTPOption interface {
|
||||
ApplyHTTPOption(Config) Config
|
||||
|
||||
// A private method to prevent users implementing the
|
||||
// interface and so future additions to it will not
|
||||
// violate compatibility.
|
||||
private()
|
||||
}
|
||||
|
||||
// GRPCOption applies an option to the gRPC driver.
|
||||
GRPCOption interface {
|
||||
ApplyGRPCOption(Config) Config
|
||||
|
||||
// A private method to prevent users implementing the
|
||||
// interface and so future additions to it will not
|
||||
// violate compatibility.
|
||||
private()
|
||||
}
|
||||
)
|
||||
|
||||
// genericOption is an option that applies the same logic
|
||||
// for both gRPC and HTTP.
|
||||
type genericOption struct {
|
||||
fn func(Config) Config
|
||||
}
|
||||
|
||||
func (g *genericOption) ApplyGRPCOption(cfg Config) Config {
|
||||
return g.fn(cfg)
|
||||
}
|
||||
|
||||
func (g *genericOption) ApplyHTTPOption(cfg Config) Config {
|
||||
return g.fn(cfg)
|
||||
}
|
||||
|
||||
func (genericOption) private() {}
|
||||
|
||||
func newGenericOption(fn func(cfg Config) Config) GenericOption {
|
||||
return &genericOption{fn: fn}
|
||||
}
|
||||
|
||||
// splitOption is an option that applies different logics
|
||||
// for gRPC and HTTP.
|
||||
type splitOption struct {
|
||||
httpFn func(Config) Config
|
||||
grpcFn func(Config) Config
|
||||
}
|
||||
|
||||
func (g *splitOption) ApplyGRPCOption(cfg Config) Config {
|
||||
return g.grpcFn(cfg)
|
||||
}
|
||||
|
||||
func (g *splitOption) ApplyHTTPOption(cfg Config) Config {
|
||||
return g.httpFn(cfg)
|
||||
}
|
||||
|
||||
func (splitOption) private() {}
|
||||
|
||||
func newSplitOption(httpFn func(cfg Config) Config, grpcFn func(cfg Config) Config) GenericOption {
|
||||
return &splitOption{httpFn: httpFn, grpcFn: grpcFn}
|
||||
}
|
||||
|
||||
// httpOption is an option that is only applied to the HTTP driver.
|
||||
type httpOption struct {
|
||||
fn func(Config) Config
|
||||
}
|
||||
|
||||
func (h *httpOption) ApplyHTTPOption(cfg Config) Config {
|
||||
return h.fn(cfg)
|
||||
}
|
||||
|
||||
func (httpOption) private() {}
|
||||
|
||||
func NewHTTPOption(fn func(cfg Config) Config) HTTPOption {
|
||||
return &httpOption{fn: fn}
|
||||
}
|
||||
|
||||
// grpcOption is an option that is only applied to the gRPC driver.
|
||||
type grpcOption struct {
|
||||
fn func(Config) Config
|
||||
}
|
||||
|
||||
func (h *grpcOption) ApplyGRPCOption(cfg Config) Config {
|
||||
return h.fn(cfg)
|
||||
}
|
||||
|
||||
func (grpcOption) private() {}
|
||||
|
||||
func NewGRPCOption(fn func(cfg Config) Config) GRPCOption {
|
||||
return &grpcOption{fn: fn}
|
||||
}
|
||||
|
||||
// Generic Options
|
||||
|
||||
// WithEndpoint configures the trace host and port only; endpoint should
|
||||
// resemble "example.com" or "localhost:4317". To configure the scheme and path,
|
||||
// use WithEndpointURL.
|
||||
func WithEndpoint(endpoint string) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Traces.Endpoint = endpoint
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
// WithEndpointURL configures the trace scheme, host, port, and path; the
|
||||
// provided value should resemble "https://example.com:4318/v1/traces".
|
||||
func WithEndpointURL(v string) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
u, err := url.Parse(v)
|
||||
if err != nil {
|
||||
global.Error(err, "otlptrace: parse endpoint url", "url", v)
|
||||
return cfg
|
||||
}
|
||||
|
||||
cfg.Traces.Endpoint = u.Host
|
||||
cfg.Traces.URLPath = u.Path
|
||||
cfg.Traces.Insecure = u.Scheme != "https"
|
||||
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithCompression(compression Compression) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Traces.Compression = compression
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithURLPath(urlPath string) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Traces.URLPath = urlPath
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithRetry(rc retry.Config) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.RetryConfig = rc
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithTLSClientConfig(tlsCfg *tls.Config) GenericOption {
|
||||
return newSplitOption(func(cfg Config) Config {
|
||||
cfg.Traces.TLSCfg = tlsCfg.Clone()
|
||||
return cfg
|
||||
}, func(cfg Config) Config {
|
||||
cfg.Traces.GRPCCredentials = credentials.NewTLS(tlsCfg)
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithInsecure() GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Traces.Insecure = true
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithSecure() GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Traces.Insecure = false
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithHeaders(headers map[string]string) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Traces.Headers = headers
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithTimeout(duration time.Duration) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Traces.Timeout = duration
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithProxy(pf HTTPTransportProxyFunc) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Traces.Proxy = pf
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
|
||||
func WithHTTPClient(c *http.Client) GenericOption {
|
||||
return newGenericOption(func(cfg Config) Config {
|
||||
cfg.Traces.HTTPClient = c
|
||||
return cfg
|
||||
})
|
||||
}
|
||||
Generated
Vendored
+40
@@ -0,0 +1,40 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/otlp/otlptrace/otlpconfig/optiontypes.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package otlpconfig // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig"
|
||||
|
||||
const (
|
||||
// DefaultCollectorGRPCPort is the default gRPC port of the collector.
|
||||
DefaultCollectorGRPCPort uint16 = 4317
|
||||
// DefaultCollectorHTTPPort is the default HTTP port of the collector.
|
||||
DefaultCollectorHTTPPort uint16 = 4318
|
||||
// DefaultCollectorHost is the host address the Exporter will attempt
|
||||
// connect to if no collector address is provided.
|
||||
DefaultCollectorHost string = "localhost"
|
||||
)
|
||||
|
||||
// Compression describes the compression used for payloads sent to the
|
||||
// collector.
|
||||
type Compression int
|
||||
|
||||
const (
|
||||
// NoCompression tells the driver to send payloads without
|
||||
// compression.
|
||||
NoCompression Compression = iota
|
||||
// GzipCompression tells the driver to send payloads after
|
||||
// compressing them with gzip.
|
||||
GzipCompression
|
||||
)
|
||||
|
||||
// Marshaler describes the kind of message format sent to the collector.
|
||||
type Marshaler int
|
||||
|
||||
const (
|
||||
// MarshalProto tells the driver to send using the protobuf binary format.
|
||||
MarshalProto Marshaler = iota
|
||||
// MarshalJSON tells the driver to send using json format.
|
||||
MarshalJSON
|
||||
)
|
||||
Generated
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/otlp/otlptrace/otlpconfig/tls.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package otlpconfig // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig"
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// CreateTLSConfig creates a tls.Config from a raw certificate bytes
|
||||
// to verify a server certificate.
|
||||
func CreateTLSConfig(certBytes []byte) (*tls.Config, error) {
|
||||
cp := x509.NewCertPool()
|
||||
if ok := cp.AppendCertsFromPEM(certBytes); !ok {
|
||||
return nil, errors.New("failed to append certificate to the cert pool")
|
||||
}
|
||||
|
||||
return &tls.Config{
|
||||
RootCAs: cp,
|
||||
}, nil
|
||||
}
|
||||
Generated
Vendored
+67
@@ -0,0 +1,67 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/otlp/partialsuccess.go
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/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)
|
||||
}
|
||||
|
||||
// As returns true if ps can be assigned to target and makes the assignment.
|
||||
// Otherwise, it returns false. This supports the errors.As() interface.
|
||||
func (ps PartialSuccess) As(target any) bool {
|
||||
t, ok := target.(*PartialSuccess)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
*t = ps
|
||||
return true
|
||||
}
|
||||
|
||||
// Is supports the errors.Is() interface.
|
||||
func (ps PartialSuccess) Is(err error) bool {
|
||||
_, ok := err.(PartialSuccess)
|
||||
return ok
|
||||
}
|
||||
|
||||
// TracePartialSuccessError returns an error describing a partial success
|
||||
// response for the trace signal.
|
||||
func TracePartialSuccessError(itemsRejected int64, errorMessage string) error {
|
||||
return PartialSuccess{
|
||||
ErrorMessage: errorMessage,
|
||||
RejectedItems: itemsRejected,
|
||||
RejectedKind: "spans",
|
||||
}
|
||||
}
|
||||
|
||||
// MetricPartialSuccessError returns an error describing a partial success
|
||||
// response for the metric signal.
|
||||
func MetricPartialSuccessError(itemsRejected int64, errorMessage string) error {
|
||||
return PartialSuccess{
|
||||
ErrorMessage: errorMessage,
|
||||
RejectedItems: itemsRejected,
|
||||
RejectedKind: "metric data points",
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+146
@@ -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/otlptrace/otlptracegrpc/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
|
||||
}
|
||||
Generated
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal"
|
||||
|
||||
// Version is the current release version of the OpenTelemetry OTLP gRPC trace
|
||||
// exporter in use.
|
||||
const Version = "1.43.0"
|
||||
Generated
Vendored
+36
@@ -0,0 +1,36 @@
|
||||
# Experimental Features
|
||||
|
||||
The `otlptracegrpc` exporter contains features that have not yet stabilized in the OpenTelemetry specification.
|
||||
These features are added to the `otlptracegrpc` exporter 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 `otlptracegrpc` exporter provides a observability feature that allows you to monitor the SDK itself.
|
||||
|
||||
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.exporter.span.inflight`
|
||||
- `otel.sdk.exporter.span.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.37.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.
|
||||
Generated
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package x // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/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
|
||||
},
|
||||
)
|
||||
Generated
Vendored
+58
@@ -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/otlptrace/otlptracegrpc].
|
||||
package x // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/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
|
||||
}
|
||||
Generated
Vendored
+211
@@ -0,0 +1,211 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package otlptracegrpc // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/otlpconfig"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/retry"
|
||||
)
|
||||
|
||||
// Option applies an option to the gRPC driver.
|
||||
type Option interface {
|
||||
applyGRPCOption(otlpconfig.Config) otlpconfig.Config
|
||||
}
|
||||
|
||||
func asGRPCOptions(opts []Option) []otlpconfig.GRPCOption {
|
||||
converted := make([]otlpconfig.GRPCOption, len(opts))
|
||||
for i, o := range opts {
|
||||
converted[i] = otlpconfig.NewGRPCOption(o.applyGRPCOption)
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
// RetryConfig defines configuration for retrying export of span batches that
|
||||
// failed to be received by the target endpoint.
|
||||
//
|
||||
// This configuration does not define any network retry strategy. That is
|
||||
// entirely handled by the gRPC ClientConn.
|
||||
type RetryConfig retry.Config
|
||||
|
||||
type wrappedOption struct {
|
||||
otlpconfig.GRPCOption
|
||||
}
|
||||
|
||||
func (w wrappedOption) applyGRPCOption(cfg otlpconfig.Config) otlpconfig.Config {
|
||||
return w.ApplyGRPCOption(cfg)
|
||||
}
|
||||
|
||||
// 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. Note, by
|
||||
// default, client security is required unless WithInsecure is used.
|
||||
//
|
||||
// This option has no effect if WithGRPCConn is used.
|
||||
func WithInsecure() Option {
|
||||
return wrappedOption{otlpconfig.WithInsecure()}
|
||||
}
|
||||
|
||||
// WithEndpoint sets the target endpoint (host and port) the Exporter will
|
||||
// connect to. The provided endpoint should resemble "example.com:4317" (no
|
||||
// scheme or path).
|
||||
//
|
||||
// If the OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
|
||||
// environment variable is set, and this option is not passed, that variable
|
||||
// value will be used. If both environment variables are set,
|
||||
// OTEL_EXPORTER_OTLP_TRACES_ENDPOINT will take precedence. If an environment
|
||||
// variable is set, and this option is passed, this option 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 wrappedOption{otlpconfig.WithEndpoint(endpoint)}
|
||||
}
|
||||
|
||||
// WithEndpointURL sets the target endpoint URL (scheme, host, port, path)
|
||||
// the Exporter will connect to. The provided endpoint URL should resemble
|
||||
// "https://example.com:4318/v1/traces".
|
||||
//
|
||||
// If the OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
|
||||
// environment variable is set, and this option is not passed, that variable
|
||||
// value will be used. If both environment variables are set,
|
||||
// OTEL_EXPORTER_OTLP_TRACES_ENDPOINT will take precedence. If an environment
|
||||
// variable is set, and this option is passed, this option 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, "https://localhost:4317/v1/traces" will be used.
|
||||
//
|
||||
// This option has no effect if WithGRPCConn is used.
|
||||
func WithEndpointURL(u string) Option {
|
||||
return wrappedOption{otlpconfig.WithEndpointURL(u)}
|
||||
}
|
||||
|
||||
// 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 wrappedOption{otlpconfig.NewGRPCOption(func(cfg otlpconfig.Config) otlpconfig.Config {
|
||||
cfg.ReconnectionPeriod = rp
|
||||
return cfg
|
||||
})}
|
||||
}
|
||||
|
||||
func compressorToCompression(compressor string) otlpconfig.Compression {
|
||||
if compressor == "gzip" {
|
||||
return otlpconfig.GzipCompression
|
||||
}
|
||||
|
||||
otel.Handle(fmt.Errorf("invalid compression type: '%s', using no compression as default", compressor))
|
||||
return otlpconfig.NoCompression
|
||||
}
|
||||
|
||||
// WithCompressor sets the compressor for the gRPC client to use when sending
|
||||
// requests. Supported compressor values: "gzip".
|
||||
func WithCompressor(compressor string) Option {
|
||||
return wrappedOption{otlpconfig.WithCompression(compressorToCompression(compressor))}
|
||||
}
|
||||
|
||||
// WithHeaders will send the provided headers with each gRPC requests.
|
||||
func WithHeaders(headers map[string]string) Option {
|
||||
return wrappedOption{otlpconfig.WithHeaders(headers)}
|
||||
}
|
||||
|
||||
// WithTLSCredentials allows the connection to use TLS credentials when
|
||||
// talking to the server. It takes in grpc.TransportCredentials instead of say
|
||||
// a Certificate file or a tls.Certificate, because the retrieving of these
|
||||
// credentials can be done in many ways e.g. plain file, in code tls.Config or
|
||||
// by certificate rotation, so it is up to the caller to decide what to use.
|
||||
//
|
||||
// This option has no effect if WithGRPCConn is used.
|
||||
func WithTLSCredentials(creds credentials.TransportCredentials) Option {
|
||||
return wrappedOption{otlpconfig.NewGRPCOption(func(cfg otlpconfig.Config) otlpconfig.Config {
|
||||
cfg.Traces.GRPCCredentials = creds
|
||||
return cfg
|
||||
})}
|
||||
}
|
||||
|
||||
// WithServiceConfig defines the default gRPC service config used.
|
||||
//
|
||||
// This option has no effect if WithGRPCConn is used.
|
||||
func WithServiceConfig(serviceConfig string) Option {
|
||||
return wrappedOption{otlpconfig.NewGRPCOption(func(cfg otlpconfig.Config) otlpconfig.Config {
|
||||
cfg.ServiceConfig = serviceConfig
|
||||
return cfg
|
||||
})}
|
||||
}
|
||||
|
||||
// WithDialOption sets explicit grpc.DialOptions to use when making a
|
||||
// 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 wrappedOption{otlpconfig.NewGRPCOption(func(cfg otlpconfig.Config) otlpconfig.Config {
|
||||
cfg.DialOptions = opts
|
||||
return cfg
|
||||
})}
|
||||
}
|
||||
|
||||
// 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 client
|
||||
// Shutdown method will not close this connection.
|
||||
func WithGRPCConn(conn *grpc.ClientConn) Option {
|
||||
return wrappedOption{otlpconfig.NewGRPCOption(func(cfg otlpconfig.Config) otlpconfig.Config {
|
||||
cfg.GRPCConn = conn
|
||||
return cfg
|
||||
})}
|
||||
}
|
||||
|
||||
// WithTimeout sets the max amount of time a client will attempt to export a
|
||||
// batch of spans. This takes precedence over any retry settings defined with
|
||||
// WithRetry, once this time limit has been reached the export is abandoned
|
||||
// and the batch of spans is dropped.
|
||||
//
|
||||
// If unset, the default timeout will be set to 10 seconds.
|
||||
func WithTimeout(duration time.Duration) Option {
|
||||
return wrappedOption{otlpconfig.WithTimeout(duration)}
|
||||
}
|
||||
|
||||
// WithRetry sets the retry policy for transient retryable errors that may be
|
||||
// returned by the target endpoint when exporting a batch of spans.
|
||||
//
|
||||
// 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(settings RetryConfig) Option {
|
||||
return wrappedOption{otlpconfig.WithRetry(retry.Config(settings))}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package otlptrace // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
||||
|
||||
// Version is the current release version of the OpenTelemetry OTLP trace exporter in use.
|
||||
func Version() string {
|
||||
return "1.43.0"
|
||||
}
|
||||
+231
@@ -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.
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
# STDOUT Trace Exporter
|
||||
|
||||
[](https://pkg.go.dev/go.opentelemetry.io/otel/exporters/stdout/stdouttrace)
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package stdouttrace // import "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultWriter = os.Stdout
|
||||
defaultPrettyPrint = false
|
||||
defaultTimestamps = true
|
||||
)
|
||||
|
||||
// config contains options for the STDOUT exporter.
|
||||
type config struct {
|
||||
// Writer is the destination. If not set, os.Stdout is used.
|
||||
Writer io.Writer
|
||||
|
||||
// PrettyPrint will encode the output into readable JSON. Default is
|
||||
// false.
|
||||
PrettyPrint bool
|
||||
|
||||
// Timestamps specifies if timestamps should be printed. Default is
|
||||
// true.
|
||||
Timestamps bool
|
||||
}
|
||||
|
||||
// newConfig creates a validated Config configured with options.
|
||||
func newConfig(options ...Option) config {
|
||||
cfg := config{
|
||||
Writer: defaultWriter,
|
||||
PrettyPrint: defaultPrettyPrint,
|
||||
Timestamps: defaultTimestamps,
|
||||
}
|
||||
for _, opt := range options {
|
||||
cfg = opt.apply(cfg)
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
// Option sets the value of an option for a Config.
|
||||
type Option interface {
|
||||
apply(config) config
|
||||
}
|
||||
|
||||
// WithWriter sets the export stream destination.
|
||||
func WithWriter(w io.Writer) Option {
|
||||
return writerOption{w}
|
||||
}
|
||||
|
||||
type writerOption struct {
|
||||
W io.Writer
|
||||
}
|
||||
|
||||
func (o writerOption) apply(cfg config) config {
|
||||
cfg.Writer = o.W
|
||||
return cfg
|
||||
}
|
||||
|
||||
// WithPrettyPrint prettifies the emitted output.
|
||||
func WithPrettyPrint() Option {
|
||||
return prettyPrintOption(true)
|
||||
}
|
||||
|
||||
type prettyPrintOption bool
|
||||
|
||||
func (o prettyPrintOption) apply(cfg config) config {
|
||||
cfg.PrettyPrint = bool(o)
|
||||
return cfg
|
||||
}
|
||||
|
||||
// WithoutTimestamps sets the export stream to not include timestamps.
|
||||
func WithoutTimestamps() Option {
|
||||
return timestampsOption(false)
|
||||
}
|
||||
|
||||
type timestampsOption bool
|
||||
|
||||
func (o timestampsOption) apply(cfg config) config {
|
||||
cfg.Timestamps = bool(o)
|
||||
return cfg
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package stdouttrace contains an OpenTelemetry exporter for tracing
|
||||
// telemetry to be written to an output destination as JSON.
|
||||
//
|
||||
// See [go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/x] for information about
|
||||
// the experimental features.
|
||||
package stdouttrace // import "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
|
||||
Generated
Vendored
+31
@@ -0,0 +1,31 @@
|
||||
// Code generated by gotmpl. DO NOT MODIFY.
|
||||
// source: internal/shared/counter/counter.go.tmpl
|
||||
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package counter provides a simple counter for generating unique IDs.
|
||||
//
|
||||
// This package is used to generate unique IDs while allowing testing packages
|
||||
// to reset the counter.
|
||||
package counter // import "go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/counter"
|
||||
|
||||
import "sync/atomic"
|
||||
|
||||
// exporterN is a global 0-based count of the number of exporters created.
|
||||
var exporterN atomic.Int64
|
||||
|
||||
// NextExporterID returns the next unique ID for an exporter.
|
||||
func NextExporterID() int64 {
|
||||
const inc = 1
|
||||
return exporterN.Add(inc) - inc
|
||||
}
|
||||
|
||||
// SetExporterID 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 SetExporterID(v int64) int64 {
|
||||
return exporterN.Swap(v)
|
||||
}
|
||||
Generated
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package internal provides internal functionality for the stdouttrace
|
||||
// package.
|
||||
package internal // import "go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal"
|
||||
|
||||
//go:generate gotmpl --body=../../../../internal/shared/counter/counter.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/counter\" }" --out=counter/counter.go
|
||||
//go:generate gotmpl --body=../../../../internal/shared/counter/counter_test.go.tmpl "--data={}" --out=counter/counter_test.go
|
||||
|
||||
//go:generate gotmpl --body=../../../../internal/shared/x/x.go.tmpl "--data={ \"pkg\": \"go.opentelemetry.io/otel/exporters/stdout/stdouttrace\" }" --out=x/x.go
|
||||
//go:generate gotmpl --body=../../../../internal/shared/x/x_test.go.tmpl "--data={}" --out=x/x_test.go
|
||||
Generated
Vendored
+230
@@ -0,0 +1,230 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package observ provides experimental observability instrumentation
|
||||
// for the stdout trace exporter.
|
||||
package observ // import "go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/observ"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal"
|
||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/x"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.40.0"
|
||||
"go.opentelemetry.io/otel/semconv/v1.40.0/otelconv"
|
||||
)
|
||||
|
||||
const (
|
||||
// ComponentType uniquely identifies the OpenTelemetry Exporter component
|
||||
// being instrumented.
|
||||
//
|
||||
// The STDOUT trace exporter is not a standardized OTel component type, so
|
||||
// it uses the Go package prefixed type name to ensure uniqueness and
|
||||
// identity.
|
||||
ComponentType = "go.opentelemetry.io/otel/exporters/stdout/stdouttrace.Exporter"
|
||||
|
||||
// ScopeName is the unique name of the meter used for instrumentation.
|
||||
ScopeName = "go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/observ"
|
||||
|
||||
// SchemaURL is the schema URL of the metrics produced by this
|
||||
// instrumentation.
|
||||
SchemaURL = semconv.SchemaURL
|
||||
|
||||
// Version is the current version of this instrumentation.
|
||||
//
|
||||
// This matches the version of the exporter.
|
||||
Version = internal.Version
|
||||
)
|
||||
|
||||
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
|
||||
},
|
||||
}
|
||||
|
||||
addOptPool = &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) {
|
||||
*s = (*s)[:0] // Reset.
|
||||
p.Put(s)
|
||||
}
|
||||
|
||||
func ComponentName(id int64) string {
|
||||
return fmt.Sprintf("%s/%d", ComponentType, id)
|
||||
}
|
||||
|
||||
// Instrumentation is experimental instrumentation for the exporter.
|
||||
type Instrumentation struct {
|
||||
inflightSpans metric.Int64UpDownCounter
|
||||
exportedSpans metric.Int64Counter
|
||||
opDuration metric.Float64Histogram
|
||||
|
||||
attrs []attribute.KeyValue
|
||||
setOpt metric.MeasurementOption
|
||||
}
|
||||
|
||||
// NewInstrumentation returns instrumentation for a STDOUT trace exporter with
|
||||
// the provided ID using the global MeterProvider.
|
||||
//
|
||||
// If the experimental observability is disabled, nil is returned.
|
||||
func NewInstrumentation(id int64) (*Instrumentation, error) {
|
||||
if !x.Observability.Enabled() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
i := &Instrumentation{
|
||||
attrs: []attribute.KeyValue{
|
||||
semconv.OTelComponentName(ComponentName(id)),
|
||||
semconv.OTelComponentTypeKey.String(ComponentType),
|
||||
},
|
||||
}
|
||||
|
||||
s := attribute.NewSet(i.attrs...)
|
||||
i.setOpt = metric.WithAttributeSet(s)
|
||||
|
||||
mp := otel.GetMeterProvider()
|
||||
m := mp.Meter(
|
||||
ScopeName,
|
||||
metric.WithInstrumentationVersion(Version),
|
||||
metric.WithSchemaURL(SchemaURL),
|
||||
)
|
||||
|
||||
var err error
|
||||
|
||||
inflightSpans, e := otelconv.NewSDKExporterSpanInflight(m)
|
||||
if e != nil {
|
||||
e = fmt.Errorf("failed to create span inflight metric: %w", e)
|
||||
err = errors.Join(err, e)
|
||||
}
|
||||
i.inflightSpans = inflightSpans.Inst()
|
||||
|
||||
exportedSpans, e := otelconv.NewSDKExporterSpanExported(m)
|
||||
if e != nil {
|
||||
e = fmt.Errorf("failed to create span exported metric: %w", e)
|
||||
err = errors.Join(err, e)
|
||||
}
|
||||
i.exportedSpans = exportedSpans.Inst()
|
||||
|
||||
opDuration, e := otelconv.NewSDKExporterOperationDuration(m)
|
||||
if e != nil {
|
||||
e = fmt.Errorf("failed to create operation duration metric: %w", e)
|
||||
err = errors.Join(err, e)
|
||||
}
|
||||
i.opDuration = opDuration.Inst()
|
||||
|
||||
return i, err
|
||||
}
|
||||
|
||||
// ExportSpans instruments the ExportSpans method of the exporter. It returns a
|
||||
// function that needs to be deferred so it is called when the method returns.
|
||||
func (i *Instrumentation) ExportSpans(ctx context.Context, nSpans int) ExportOp {
|
||||
start := time.Now()
|
||||
|
||||
if i.inflightSpans.Enabled(ctx) {
|
||||
addOpt := get[metric.AddOption](addOptPool)
|
||||
defer put(addOptPool, addOpt)
|
||||
*addOpt = append(*addOpt, i.setOpt)
|
||||
i.inflightSpans.Add(ctx, int64(nSpans), *addOpt...)
|
||||
}
|
||||
|
||||
return ExportOp{
|
||||
ctx: ctx,
|
||||
start: start,
|
||||
nSpans: int64(nSpans),
|
||||
inst: i,
|
||||
}
|
||||
}
|
||||
|
||||
// ExportOp is an in-progress ExportSpans operation.
|
||||
type ExportOp struct {
|
||||
ctx context.Context
|
||||
start time.Time
|
||||
nSpans int64
|
||||
inst *Instrumentation
|
||||
}
|
||||
|
||||
// End ends the ExportSpans operation, recording its success and duration.
|
||||
//
|
||||
// The success parameter indicates how many spans were successfully exported.
|
||||
// The err parameter indicates whether the operation failed. If err is not nil,
|
||||
// the number of failed spans (nSpans - success) is also recorded.
|
||||
func (e ExportOp) End(success int64, err error) {
|
||||
inflightSpansEnable := e.inst.inflightSpans.Enabled(e.ctx)
|
||||
exportedSpansEnable := e.inst.exportedSpans.Enabled(e.ctx)
|
||||
opDurationEnable := e.inst.opDuration.Enabled(e.ctx)
|
||||
|
||||
if !inflightSpansEnable && !exportedSpansEnable && !opDurationEnable {
|
||||
return
|
||||
}
|
||||
|
||||
addOpt := get[metric.AddOption](addOptPool)
|
||||
defer put(addOptPool, addOpt)
|
||||
*addOpt = append(*addOpt, e.inst.setOpt)
|
||||
|
||||
if inflightSpansEnable {
|
||||
e.inst.inflightSpans.Add(e.ctx, -e.nSpans, *addOpt...)
|
||||
}
|
||||
|
||||
// Record the success and duration of the operation.
|
||||
//
|
||||
// Do not exclude 0 values, as they are valid and indicate no spans
|
||||
// were exported which is meaningful for certain aggregations.
|
||||
if exportedSpansEnable {
|
||||
e.inst.exportedSpans.Add(e.ctx, success, *addOpt...)
|
||||
}
|
||||
|
||||
mOpt := e.inst.setOpt
|
||||
if err != nil && exportedSpansEnable {
|
||||
attrs := get[attribute.KeyValue](measureAttrsPool)
|
||||
defer put(measureAttrsPool, attrs)
|
||||
*attrs = append(*attrs, e.inst.attrs...)
|
||||
*attrs = append(*attrs, semconv.ErrorType(err))
|
||||
|
||||
// Do not inefficiently make a copy of attrs by using
|
||||
// WithAttributes instead of WithAttributeSet.
|
||||
set := attribute.NewSet(*attrs...)
|
||||
mOpt = metric.WithAttributeSet(set)
|
||||
|
||||
// Reset addOpt with new attribute set.
|
||||
*addOpt = append((*addOpt)[:0], mOpt)
|
||||
|
||||
e.inst.exportedSpans.Add(e.ctx, e.nSpans-success, *addOpt...)
|
||||
}
|
||||
|
||||
if opDurationEnable {
|
||||
recordOpt := get[metric.RecordOption](recordOptPool)
|
||||
defer put(recordOptPool, recordOpt)
|
||||
*recordOpt = append(*recordOpt, mOpt)
|
||||
e.inst.opDuration.Record(e.ctx, time.Since(e.start).Seconds(), *recordOpt...)
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package internal // import "go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal"
|
||||
|
||||
// Version is the current release version of the OpenTelemetry stdouttrace
|
||||
// exporter in use.
|
||||
const Version = "1.43.0"
|
||||
Generated
Vendored
+36
@@ -0,0 +1,36 @@
|
||||
# Experimental Features
|
||||
|
||||
The `stdouttrace` exporter contains features that have not yet stabilized in the OpenTelemetry specification.
|
||||
These features are added to the `stdouttrace` 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 `stdouttrace` 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 SDK will create the following metrics using the global `MeterProvider`:
|
||||
|
||||
- `otel.sdk.exporter.span.inflight`
|
||||
- `otel.sdk.exporter.span.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.
|
||||
Generated
Vendored
+23
@@ -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/stdout/stdouttrace].
|
||||
package x // import "go.opentelemetry.io/otel/exporters/stdout/stdouttrace/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", "SELF_OBSERVABILITY"},
|
||||
func(v string) (string, bool) {
|
||||
if strings.EqualFold(v, "true") {
|
||||
return v, true
|
||||
}
|
||||
return "", false
|
||||
},
|
||||
)
|
||||
Generated
Vendored
+58
@@ -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/stdout/stdouttrace].
|
||||
package x // import "go.opentelemetry.io/otel/exporters/stdout/stdouttrace/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
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package stdouttrace // import "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/counter"
|
||||
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace/internal/observ"
|
||||
"go.opentelemetry.io/otel/sdk/trace"
|
||||
"go.opentelemetry.io/otel/sdk/trace/tracetest"
|
||||
)
|
||||
|
||||
var zeroTime time.Time
|
||||
|
||||
var _ trace.SpanExporter = &Exporter{}
|
||||
|
||||
// New creates an Exporter with the passed options.
|
||||
func New(options ...Option) (*Exporter, error) {
|
||||
cfg := newConfig(options...)
|
||||
|
||||
enc := json.NewEncoder(cfg.Writer)
|
||||
if cfg.PrettyPrint {
|
||||
enc.SetIndent("", "\t")
|
||||
}
|
||||
|
||||
exporter := &Exporter{
|
||||
encoder: enc,
|
||||
timestamps: cfg.Timestamps,
|
||||
}
|
||||
|
||||
var err error
|
||||
exporter.inst, err = observ.NewInstrumentation(counter.NextExporterID())
|
||||
return exporter, err
|
||||
}
|
||||
|
||||
// Exporter is an implementation of trace.SpanSyncer that writes spans to stdout.
|
||||
type Exporter struct {
|
||||
encoder *json.Encoder
|
||||
encoderMu sync.Mutex
|
||||
timestamps bool
|
||||
|
||||
stoppedMu sync.RWMutex
|
||||
stopped bool
|
||||
|
||||
inst *observ.Instrumentation
|
||||
}
|
||||
|
||||
// ExportSpans writes spans in json format to stdout.
|
||||
func (e *Exporter) ExportSpans(ctx context.Context, spans []trace.ReadOnlySpan) (err error) {
|
||||
var success int64
|
||||
if e.inst != nil {
|
||||
op := e.inst.ExportSpans(ctx, len(spans))
|
||||
defer func() { op.End(success, err) }()
|
||||
}
|
||||
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
e.stoppedMu.RLock()
|
||||
stopped := e.stopped
|
||||
e.stoppedMu.RUnlock()
|
||||
if stopped {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(spans) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
stubs := tracetest.SpanStubsFromReadOnlySpans(spans)
|
||||
|
||||
e.encoderMu.Lock()
|
||||
defer e.encoderMu.Unlock()
|
||||
for i := range stubs {
|
||||
stub := &stubs[i]
|
||||
// Remove timestamps
|
||||
if !e.timestamps {
|
||||
stub.StartTime = zeroTime
|
||||
stub.EndTime = zeroTime
|
||||
for j := range stub.Events {
|
||||
ev := &stub.Events[j]
|
||||
ev.Time = zeroTime
|
||||
}
|
||||
}
|
||||
|
||||
// Encode span stubs, one by one
|
||||
if e := e.encoder.Encode(stub); e != nil {
|
||||
err = errors.Join(err, fmt.Errorf("failed to encode span %d: %w", i, e))
|
||||
continue
|
||||
}
|
||||
success++
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Shutdown is called to stop the exporter, it performs no action.
|
||||
func (e *Exporter) Shutdown(context.Context) error {
|
||||
e.stoppedMu.Lock()
|
||||
e.stopped = true
|
||||
e.stoppedMu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalLog is the marshaling function used by the logging system to represent this Exporter.
|
||||
func (e *Exporter) MarshalLog() any {
|
||||
return struct {
|
||||
Type string
|
||||
WithTimestamps bool
|
||||
}{
|
||||
Type: "stdout",
|
||||
WithTimestamps: e.timestamps,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user