package tracing // Copyright 2018 Microsoft Corporation // // 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. import ( "context" "fmt" "net/http" "os" "contrib.go.opencensus.io/exporter/ocagent" "go.opencensus.io/plugin/ochttp" "go.opencensus.io/plugin/ochttp/propagation/tracecontext" "go.opencensus.io/stats/view" "go.opencensus.io/trace" ) var ( // Transport is the default tracing RoundTripper. The custom options setter will control // if traces are being emitted or not. Transport = &ochttp.Transport{ Propagation: &tracecontext.HTTPFormat{}, GetStartOptions: getStartOptions, } // enabled is the flag for marking if tracing is enabled. enabled = false // Sampler is the tracing sampler. If tracing is disabled it will never sample. Otherwise // it will be using the parent sampler or the default. sampler = trace.NeverSample() // Views for metric instrumentation. views = map[string]*view.View{} // the trace exporter traceExporter trace.Exporter ) func init() { enableFromEnv() } func enableFromEnv() { _, ok := os.LookupEnv("AZURE_SDK_TRACING_ENABLED") _, legacyOk := os.LookupEnv("AZURE_SDK_TRACING_ENABELD") if ok || legacyOk { agentEndpoint, ok := os.LookupEnv("OCAGENT_TRACE_EXPORTER_ENDPOINT") if ok { EnableWithAIForwarding(agentEndpoint) } else { Enable() } } } // IsEnabled returns true if monitoring is enabled for the sdk. func IsEnabled() bool { return enabled } // Enable will start instrumentation for metrics and traces. func Enable() error { enabled = true sampler = nil err := initStats() return err } // Disable will disable instrumentation for metrics and traces. func Disable() { disableStats() sampler = trace.NeverSample() if traceExporter != nil { trace.UnregisterExporter(traceExporter) } enabled = false } // EnableWithAIForwarding will start instrumentation and will connect to app insights forwarder // exporter making the metrics and traces available in app insights. func EnableWithAIForwarding(agentEndpoint string) (err error) { err = Enable() if err != nil { return err } traceExporter, err := ocagent.NewExporter(ocagent.WithInsecure(), ocagent.WithAddress(agentEndpoint)) if err != nil { return err } trace.RegisterExporter(traceExporter) return } // getStartOptions is the custom options setter for the ochttp package. func getStartOptions(*http.Request) trace.StartOptions { return trace.StartOptions{ Sampler: sampler, } } // initStats registers the views for the http metrics func initStats() (err error) { clientViews := []*view.View{ ochttp.ClientCompletedCount, ochttp.ClientRoundtripLatencyDistribution, ochttp.ClientReceivedBytesDistribution, ochttp.ClientSentBytesDistribution, } for _, cv := range clientViews { vn := fmt.Sprintf("Azure/go-autorest/tracing-%s", cv.Name) views[vn] = cv.WithName(vn) err = view.Register(views[vn]) if err != nil { return err } } return } // disableStats will unregister the previously registered metrics func disableStats() { for _, v := range views { view.Unregister(v) } } // StartSpan starts a trace span func StartSpan(ctx context.Context, name string) context.Context { ctx, _ = trace.StartSpan(ctx, name, trace.WithSampler(sampler)) return ctx } // EndSpan ends a previously started span stored in the context func EndSpan(ctx context.Context, httpStatusCode int, err error) { span := trace.FromContext(ctx) if span == nil { return } if err != nil { span.SetStatus(trace.Status{Message: err.Error(), Code: toTraceStatusCode(httpStatusCode)}) } span.End() } // toTraceStatusCode converts HTTP Codes to OpenCensus codes as defined // at https://github.com/census-instrumentation/opencensus-specs/blob/master/trace/HTTP.md#status func toTraceStatusCode(httpStatusCode int) int32 { switch { case http.StatusOK <= httpStatusCode && httpStatusCode < http.StatusBadRequest: return trace.StatusCodeOK case httpStatusCode == http.StatusBadRequest: return trace.StatusCodeInvalidArgument case httpStatusCode == http.StatusUnauthorized: // 401 is actually unauthenticated. return trace.StatusCodeUnauthenticated case httpStatusCode == http.StatusForbidden: return trace.StatusCodePermissionDenied case httpStatusCode == http.StatusNotFound: return trace.StatusCodeNotFound case httpStatusCode == http.StatusTooManyRequests: return trace.StatusCodeResourceExhausted case httpStatusCode == 499: return trace.StatusCodeCancelled case httpStatusCode == http.StatusNotImplemented: return trace.StatusCodeUnimplemented case httpStatusCode == http.StatusServiceUnavailable: return trace.StatusCodeUnavailable case httpStatusCode == http.StatusGatewayTimeout: return trace.StatusCodeDeadlineExceeded default: return trace.StatusCodeUnknown } }