Skip to content

Add oterror package and implementation in api/global #778

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jun 2, 2020
92 changes: 92 additions & 0 deletions api/global/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package global

import (
"log"
"os"
"sync"
"sync/atomic"

"go.opentelemetry.io/otel/api/oterror"
)

var (
// globalHandler provides an oterror.Handler that can be used throughout
// an OpenTelemetry instrumented project. When a user specified Handler
// is registered (`SetHandler`) all calls to `Handle` will be delegated
// to the registered Handler.
globalHandler = &handler{
l: log.New(os.Stderr, "", log.LstdFlags),
}

// delegateHanderOnce ensures that a user provided Handler is only ever
// registered once.
delegateHanderOnce sync.Once

// Ensure the handler implements oterror.Handle at build time.
_ oterror.Handler = (*handler)(nil)
)

// handler logs all errors to STDERR.
type handler struct {
delegate atomic.Value

l *log.Logger
}

// setDelegate sets the handler delegate if one is not already set.
func (h *handler) setDelegate(d oterror.Handler) {
if h.delegate.Load() != nil {
// Delegate already registered
return
}
h.delegate.Store(d)
}

// Handle implements oterror.Handler.
func (h *handler) Handle(err error) {
if d := h.delegate.Load(); d != nil {
d.(oterror.Handler).Handle(err)
return
}
h.l.Print(err)
}

// Handler returns the global Handler instance. If no Handler instance has
// be explicitly set yet, a default Handler is returned that logs to STDERR
// until an Handler is set (all functionality is delegated to the set
// Handler once it is set).
func Handler() oterror.Handler {
return globalHandler
}

// SetHandler sets the global Handler to be h.
func SetHandler(h oterror.Handler) {
delegateHanderOnce.Do(func() {
current := Handler()
if current == h {
return
}
if internalHandler, ok := current.(*handler); ok {
internalHandler.setDelegate(h)
}
})
}

// Handle is a convience function for Handler().Handle(err)
func Handle(err error) {
globalHandler.Handle(err)
}
137 changes: 137 additions & 0 deletions api/global/handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package global

import (
"bytes"
"errors"
"log"
"testing"
"time"

"github.com/stretchr/testify/suite"
)

type errLogger []string

func (l *errLogger) Write(p []byte) (int, error) {
msg := bytes.TrimRight(p, "\n")
(*l) = append(*l, string(msg))
return len(msg), nil
}

func (l *errLogger) Reset() {
*l = errLogger([]string{})
}

func (l *errLogger) Got() []string {
return []string(*l)
}

type HandlerTestSuite struct {
suite.Suite

origHandler *handler
errLogger *errLogger
}

func (s *HandlerTestSuite) SetupSuite() {
s.errLogger = new(errLogger)
s.origHandler = globalHandler
globalHandler = &handler{
l: log.New(s.errLogger, "", 0),
}
}

func (s *HandlerTestSuite) TearDownSuite() {
globalHandler = s.origHandler
}

func (s *HandlerTestSuite) SetupTest() {
s.errLogger.Reset()
}

func (s *HandlerTestSuite) TestGlobalHandler() {
errs := []string{"one", "two"}
Handler().Handle(errors.New(errs[0]))
Handle(errors.New(errs[1]))
s.Assert().Equal(errs, s.errLogger.Got())
}

func (s *HandlerTestSuite) TestNoDropsOnDelegate() {
var sent int
err := errors.New("")
stop := make(chan struct{})
beat := make(chan struct{})
done := make(chan struct{})

go func() {
for {
select {
case <-stop:
done <- struct{}{}
return
default:
sent++
Handle(err)
}

select {
case beat <- struct{}{}:
default:
}
}
}()

// Wait for the spice to flow
select {
case <-time.Tick(2 * time.Millisecond):
s.T().Fatal("no errors were sent in 2ms")
case <-beat:
}

// Change to another Handler. We are testing this is loss-less.
newErrLogger := new(errLogger)
secondary := &handler{
l: log.New(newErrLogger, "", 0),
}
SetHandler(secondary)

select {
case <-time.Tick(2 * time.Millisecond):
s.T().Fatal("no errors were sent within 2ms after SetHandler")
case <-beat:
}

// Now beat is clear, wait for a fresh send.
select {
case <-time.Tick(2 * time.Millisecond):
s.T().Fatal("no fresh errors were sent within 2ms after SetHandler")
case <-beat:
}

// Stop sending errors.
stop <- struct{}{}
// Ensure we do not lose any straglers.
<-done

got := append(s.errLogger.Got(), newErrLogger.Got()...)
s.Assert().Greater(len(got), 1, "at least 2 errors should have been sent")
s.Assert().Len(got, sent)
}

func TestHandlerTestSuite(t *testing.T) {
suite.Run(t, new(HandlerTestSuite))
}
38 changes: 0 additions & 38 deletions api/global/global.go → api/global/metric.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,34 +17,8 @@ package global
import (
"go.opentelemetry.io/otel/api/global/internal"
"go.opentelemetry.io/otel/api/metric"
"go.opentelemetry.io/otel/api/propagation"
"go.opentelemetry.io/otel/api/trace"
)

// Tracer creates a named tracer that implements Tracer interface.
// If the name is an empty string then provider uses default name.
//
// This is short for TraceProvider().Tracer(name)
func Tracer(name string) trace.Tracer {
return TraceProvider().Tracer(name)
}

// TraceProvider returns the registered global trace provider.
// If none is registered then an instance of trace.NoopProvider is returned.
//
// Use the trace provider to create a named tracer. E.g.
// tracer := global.TraceProvider().Tracer("example.com/foo")
// or
// tracer := global.Tracer("example.com/foo")
func TraceProvider() trace.Provider {
return internal.TraceProvider()
}

// SetTraceProvider registers `tp` as the global trace provider.
func SetTraceProvider(tp trace.Provider) {
internal.SetTraceProvider(tp)
}

// Meter gets a named Meter interface. If the name is an
// empty string, the provider uses a default name.
//
Expand All @@ -69,15 +43,3 @@ func MeterProvider() metric.Provider {
func SetMeterProvider(mp metric.Provider) {
internal.SetMeterProvider(mp)
}

// Propagators returns the registered global propagators instance. If
// none is registered then an instance of propagators.NoopPropagators
// is returned.
func Propagators() propagation.Propagators {
return internal.Propagators()
}

// SetPropagators registers `p` as the global propagators instance.
func SetPropagators(p propagation.Propagators) {
internal.SetPropagators(p)
}
43 changes: 43 additions & 0 deletions api/global/metric_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package global_test

import (
"testing"

"go.opentelemetry.io/otel/api/global"
"go.opentelemetry.io/otel/api/metric"
)

type testMeterProvider struct{}

var _ metric.Provider = &testMeterProvider{}

func (*testMeterProvider) Meter(_ string) metric.Meter {
return metric.Meter{}
}

func TestMultipleGlobalMeterProvider(t *testing.T) {
p1 := testMeterProvider{}
p2 := metric.NoopProvider{}
global.SetMeterProvider(&p1)
global.SetMeterProvider(&p2)

got := global.MeterProvider()
want := &p2
if got != want {
t.Fatalf("Provider: got %p, want %p\n", got, want)
}
}
32 changes: 32 additions & 0 deletions api/global/propagation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package global

import (
"go.opentelemetry.io/otel/api/global/internal"
"go.opentelemetry.io/otel/api/propagation"
)

// Propagators returns the registered global propagators instance. If
// none is registered then an instance of propagators.NoopPropagators
// is returned.
func Propagators() propagation.Propagators {
return internal.Propagators()
}

// SetPropagators registers `p` as the global propagators instance.
func SetPropagators(p propagation.Propagators) {
internal.SetPropagators(p)
}
Loading