Skip to content

Commit

Permalink
Merge pull request #778 from MrAlias/oterror
Browse files Browse the repository at this point in the history
Add oterror package and implementation in api/global
  • Loading branch information
MrAlias authored Jun 2, 2020
2 parents 3ee928a + af0c2d6 commit d9b6d9c
Show file tree
Hide file tree
Showing 12 changed files with 425 additions and 72 deletions.
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

0 comments on commit d9b6d9c

Please sign in to comment.