diff --git a/sdk/internal/runtime/runtime.go b/sdk/internal/diag/diag.go similarity index 53% rename from sdk/internal/runtime/runtime.go rename to sdk/internal/diag/diag.go index 6154c24e3986..332ee7d754d4 100644 --- a/sdk/internal/runtime/runtime.go +++ b/sdk/internal/diag/diag.go @@ -3,7 +3,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package runtime +package diag import ( "fmt" @@ -11,14 +11,30 @@ import ( "strings" ) +// Caller returns the file and line number of a frame on the caller's stack. +// If the funtion fails an empty string is returned. +// skipFrames - the number of frames to skip when determining the caller. +// Passing a value of 0 will return the immediate caller of this function. +func Caller(skipFrames int) string { + if pc, file, line, ok := runtime.Caller(skipFrames + 1); ok { + // the skipFrames + 1 is to skip ourselves + frame := runtime.FuncForPC(pc) + return fmt.Sprintf("%s()\n\t%s:%d", frame.Name(), file, line) + } + return "" +} + // StackTrace returns a formatted stack trace string. +// If the funtion fails an empty string is returned. // skipFrames - the number of stack frames to skip before composing the trace string. // totalFrames - the maximum number of stack frames to include in the trace string. func StackTrace(skipFrames, totalFrames int) string { - sb := strings.Builder{} pcCallers := make([]uintptr, totalFrames) - runtime.Callers(skipFrames, pcCallers) + if frames := runtime.Callers(skipFrames, pcCallers); frames == 0 { + return "" + } frames := runtime.CallersFrames(pcCallers) + sb := strings.Builder{} for { frame, more := frames.Next() sb.WriteString(frame.Function) diff --git a/sdk/internal/runtime/runtime_test.go b/sdk/internal/diag/diag_test.go similarity index 68% rename from sdk/internal/runtime/runtime_test.go rename to sdk/internal/diag/diag_test.go index 500806f4421e..7aa91ddaf908 100644 --- a/sdk/internal/runtime/runtime_test.go +++ b/sdk/internal/diag/diag_test.go @@ -3,13 +3,36 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package runtime +package diag import ( + "regexp" "strings" "testing" ) +func TestCallerBasic(t *testing.T) { + c := Caller(0) + matched, err := regexp.MatchString(`/diag_test.go:\d+$`, c) + if err != nil { + t.Fatal(err) + } + if !matched { + t.Fatalf("got %s", c) + } +} + +func TestCallerSkipFrame(t *testing.T) { + c := Caller(1) + matched, err := regexp.MatchString(`/testing.go:\d+$`, c) + if err != nil { + t.Fatal(err) + } + if !matched { + t.Fatalf("got %s", c) + } +} + func TestStackTraceBasic(t *testing.T) { trace := StackTrace(0, 1) trace = strings.TrimSpace(trace) @@ -24,7 +47,7 @@ func TestStackTraceSkipFrame(t *testing.T) { trace := StackTrace(1, 1) trace = strings.TrimSpace(trace) parts := strings.Split(trace, "\n") - const topFrame = "runtime.StackTrace()" + const topFrame = "diag.StackTrace()" if strings.LastIndex(parts[0], topFrame) == -1 { t.Fatalf("%s didn't end with %s", parts[0], topFrame) } diff --git a/sdk/internal/log/log.go b/sdk/internal/log/log.go index 7b367e3f7e82..e101ea817883 100644 --- a/sdk/internal/log/log.go +++ b/sdk/internal/log/log.go @@ -31,15 +31,10 @@ const ( LongRunningOperation Classification = "LongRunningOperation" ) -// Listener is the funciton signature invoked when writing log entries. -// A Listener is required to perform its own synchronization if it's expected to be called -// from multiple Go routines -type Listener func(Classification, string) - // logger controls which classifications to log and writing to the underlying log. type logger struct { cls []Classification - lst Listener + lst func(Classification, string) } // SetClassifications is used to control which classifications are written to @@ -48,8 +43,8 @@ func SetClassifications(cls ...Classification) { log.cls = cls } -// SetListener will set the Logger to write to the specified Listener. -func SetListener(lst Listener) { +// SetListener will set the Logger to write to the specified listener. +func SetListener(lst func(Classification, string)) { log.lst = lst } @@ -74,7 +69,7 @@ func Should(cls Classification) bool { return false } -// Write invokes the underlying Listener with the specified classification and message. +// Write invokes the underlying listener with the specified classification and message. // If the classification shouldn't be logged or there is no listener then Write does nothing. func Write(cls Classification, message string) { if !Should(cls) { @@ -83,7 +78,7 @@ func Write(cls Classification, message string) { log.lst(cls, message) } -// Writef invokes the underlying Listener with the specified classification and formatted message. +// Writef invokes the underlying listener with the specified classification and formatted message. // If the classification shouldn't be logged or there is no listener then Writef does nothing. func Writef(cls Classification, format string, a ...interface{}) { if !Should(cls) { diff --git a/sdk/internal/runtime/frame_error.go b/sdk/internal/runtime/frame_error.go deleted file mode 100644 index 62dccf87b876..000000000000 --- a/sdk/internal/runtime/frame_error.go +++ /dev/null @@ -1,45 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package runtime - -import ( - "fmt" - "runtime" -) - -// NewFrameError wraps the specified error with an error that provides stack frame information. -// Call this at the inner error's origin to provide file name and line number info with the error. -// You MUST supply an inner error. -// DO NOT ARBITRARILY CALL THIS TO WRAP ERRORS! There MUST be only ONE error of this type in the chain. -func NewFrameError(inner error, stackTrace bool, skipFrames, totalFrames int) error { - fe := FrameError{inner: inner, info: "stack trace unavailable"} - if stackTrace { - // the skipFrames+3 is to skip runtime.Callers(), StackTrace and ourselves - fe.info = StackTrace(skipFrames+3, totalFrames) - } else if pc, file, line, ok := runtime.Caller(skipFrames + 1); ok { - // the skipFrames + 1 is to skip ourselves - frame := runtime.FuncForPC(pc) - fe.info = fmt.Sprintf("%s()\n\t%s:%d\n", frame.Name(), file, line) - } - return &fe -} - -// FrameError associates an error with stack frame information. -// Exported for testing purposes, use NewFrameError(). -type FrameError struct { - inner error - info string -} - -// Error implements the error interface for type FrameError. -func (f *FrameError) Error() string { - return fmt.Sprintf("%s:\n%s\n", f.inner.Error(), f.info) -} - -// Unwrap returns the inner error. -func (f *FrameError) Unwrap() error { - return f.inner -} diff --git a/sdk/internal/runtime/frame_error_test.go b/sdk/internal/runtime/frame_error_test.go deleted file mode 100644 index 1f144e7fd115..000000000000 --- a/sdk/internal/runtime/frame_error_test.go +++ /dev/null @@ -1,42 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package runtime - -import ( - "errors" - "strings" - "testing" -) - -func TestFrameError(t *testing.T) { - err := NewFrameError(errors.New("failed"), false, 0, 0) - trace := strings.TrimSpace(err.Error()) - parts := strings.Split(trace, "\n") - // parts will be three: - // failed: - // path/to/TestFrameError() - // - const length = 3 - if l := len(parts); l != length { - t.Fatalf("expected %d frames, got %d", length, l) - } - if strings.LastIndex(parts[1], "TestFrameError()") == -1 { - t.Fatalf("didn't find TestFrameError() in %s", parts[1]) - } -} - -func TestFrameErrorWithStack(t *testing.T) { - err := NewFrameError(errors.New("failed"), true, 0, 5) - trace := strings.TrimSpace(err.Error()) - parts := strings.Split(trace, "\n") - if l := len(parts); l < 3 { - t.Fatalf("not enough frames, got %d", l) - } - // parts will be more than three but with the same top-most values as previous test - if strings.LastIndex(parts[1], "TestFrameErrorWithStack()") == -1 { - t.Fatalf("didn't find TestFrameErrorWithStack() in %s", parts[1]) - } -} diff --git a/sdk/internal/runtime/response_error.go b/sdk/internal/runtime/response_error.go deleted file mode 100644 index 20c51d5f2355..000000000000 --- a/sdk/internal/runtime/response_error.go +++ /dev/null @@ -1,43 +0,0 @@ -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package runtime - -import "net/http" - -// NewResponseError wraps the specified error with an error that provides access to an HTTP response. -// If an HTTP request fails, wrap the response and the associated error in this error type so that -// callers can access the underlying *http.Response as required. -// You MUST supply an inner error. -func NewResponseError(inner error, resp *http.Response) error { - return &ResponseError{inner: inner, resp: resp} -} - -// ResponseError associates an error with an HTTP response. -// Exported for type assertion purposes in azcore, use NewResponseError(). -type ResponseError struct { - inner error - resp *http.Response -} - -// Error implements the error interface for type ResponseError. -func (e *ResponseError) Error() string { - return e.inner.Error() -} - -// Unwrap returns the inner error. -func (e *ResponseError) Unwrap() error { - return e.inner -} - -// RawResponse returns the HTTP response associated with this error. -func (e *ResponseError) RawResponse() *http.Response { - return e.resp -} - -// NonRetriable indicates this error is non-transient. -func (e *ResponseError) NonRetriable() { - // marker method -}