github.com/mailgun/holster/v4@v4.20.0/tracing/scope.go (about)

     1  // Trace a code block as a scoped span.
     2  // * Use instead of manual instrumentation: `tracer.Start()`/`span.End()`.
     3  // * Must call `InitTracing()` first.
     4  // * Automates start/end of span.
     5  // * Tags file and line number where span started.
     6  // * If function returned error:
     7  //   * Span is tagged as error.
     8  //   * Sets span attributes `otel.status_code` and `otel.status_description`
     9  //     with error details.
    10  //   * Logs error details to span.
    11  
    12  package tracing
    13  
    14  import (
    15  	"context"
    16  	"runtime"
    17  	"strconv"
    18  
    19  	"github.com/mailgun/holster/v4/errors"
    20  	"go.opentelemetry.io/otel/attribute"
    21  	"go.opentelemetry.io/otel/codes"
    22  	"go.opentelemetry.io/otel/trace"
    23  )
    24  
    25  type ScopeAction func(ctx context.Context) error
    26  
    27  const (
    28  	ErrorClassKey = "error.class"
    29  	ErrorTypeKey  = "error.type"
    30  )
    31  
    32  // StartScope start a scope with span named after fully qualified caller function.
    33  func StartScope(ctx context.Context, opts ...trace.SpanStartOption) context.Context {
    34  	return startScope(ctx, InfoLevel, 1, opts...)
    35  }
    36  
    37  // StartScopeDebug start a scope with span named after fully qualified caller function with
    38  // debug log level.
    39  func StartScopeDebug(ctx context.Context, opts ...trace.SpanStartOption) context.Context {
    40  	return startScope(ctx, DebugLevel, 1, opts...)
    41  }
    42  
    43  // StartScopeInfo start a scope with span named after fully qualified caller function with
    44  // info log level.
    45  func StartScopeInfo(ctx context.Context, opts ...trace.SpanStartOption) context.Context {
    46  	return startScope(ctx, InfoLevel, 1, opts...)
    47  }
    48  
    49  // StartScopeWarn start a scope with span named after fully qualified caller function with
    50  // warning log level.
    51  func StartScopeWarn(ctx context.Context, opts ...trace.SpanStartOption) context.Context {
    52  	return startScope(ctx, WarnLevel, 1, opts...)
    53  }
    54  
    55  // StartScopeError start a scope with span named after fully qualified caller function with
    56  // error log level.
    57  func StartScopeError(ctx context.Context, opts ...trace.SpanStartOption) context.Context {
    58  	return startScope(ctx, ErrorLevel, 1, opts...)
    59  }
    60  
    61  // Start a scope with user-provided span name.
    62  func StartNamedScope(ctx context.Context, spanName string, opts ...trace.SpanStartOption) context.Context {
    63  	return startNamedScope(ctx, spanName, InfoLevel, 1, opts...)
    64  }
    65  
    66  // StartNamedScopeDebug start a scope with user-provided span name with debug log level.
    67  func StartNamedScopeDebug(ctx context.Context, spanName string, opts ...trace.SpanStartOption) context.Context {
    68  	return startNamedScope(ctx, spanName, DebugLevel, 1, opts...)
    69  }
    70  
    71  // StartNamedScopeInfo start a scope with user-provided span name with info log level.
    72  func StartNamedScopeInfo(ctx context.Context, spanName string, opts ...trace.SpanStartOption) context.Context {
    73  	return startNamedScope(ctx, spanName, InfoLevel, 1, opts...)
    74  }
    75  
    76  // StartNamedScopeWarn start a scope with user-provided span name with warning log level.
    77  func StartNamedScopeWarn(ctx context.Context, spanName string, opts ...trace.SpanStartOption) context.Context {
    78  	return startNamedScope(ctx, spanName, WarnLevel, 1, opts...)
    79  }
    80  
    81  // StartNamedScopeError start a scope with user-provided span name with error log level.
    82  func StartNamedScopeError(ctx context.Context, spanName string, opts ...trace.SpanStartOption) context.Context {
    83  	return startNamedScope(ctx, spanName, ErrorLevel, 1, opts...)
    84  }
    85  
    86  // BranchScope branch an existing scope with span named after fully qualified caller function.
    87  func BranchScope(ctx context.Context, opts ...trace.SpanStartOption) context.Context {
    88  	if !trace.SpanContextFromContext(ctx).IsValid() {
    89  		return ctx
    90  	}
    91  	return startScope(ctx, InfoLevel, 1, opts...)
    92  }
    93  
    94  // BranchScopeDebug branch an existing scope with span named after fully qualified caller function with
    95  // debug log level.
    96  func BranchScopeDebug(ctx context.Context, opts ...trace.SpanStartOption) context.Context {
    97  	if !trace.SpanContextFromContext(ctx).IsValid() {
    98  		return ctx
    99  	}
   100  	return startScope(ctx, DebugLevel, 1, opts...)
   101  }
   102  
   103  // BranchScopeInfo branch an existing scope with span named after fully qualified caller function with
   104  // info log level.
   105  func BranchScopeInfo(ctx context.Context, opts ...trace.SpanStartOption) context.Context {
   106  	if !trace.SpanContextFromContext(ctx).IsValid() {
   107  		return ctx
   108  	}
   109  	return startScope(ctx, InfoLevel, 1, opts...)
   110  }
   111  
   112  // BranchScopeWarn branch an existing scope with span named after fully qualified caller function with
   113  // warn log level.
   114  func BranchScopeWarn(ctx context.Context, opts ...trace.SpanStartOption) context.Context {
   115  	if !trace.SpanContextFromContext(ctx).IsValid() {
   116  		return ctx
   117  	}
   118  	return startScope(ctx, WarnLevel, 1, opts...)
   119  }
   120  
   121  // BranchScopeError branch an existing scope with span named after fully qualified caller function with
   122  // error log level.
   123  func BranchScopeError(ctx context.Context, opts ...trace.SpanStartOption) context.Context {
   124  	if !trace.SpanContextFromContext(ctx).IsValid() {
   125  		return ctx
   126  	}
   127  	return startScope(ctx, ErrorLevel, 1, opts...)
   128  }
   129  
   130  // BranchNamedScope branch an existing scope with user-provided span name.
   131  func BranchNamedScope(ctx context.Context, spanName string, opts ...trace.SpanStartOption) context.Context {
   132  	if !trace.SpanContextFromContext(ctx).IsValid() {
   133  		return ctx
   134  	}
   135  	return startNamedScope(ctx, spanName, InfoLevel, 1, opts...)
   136  }
   137  
   138  // BranchNamedScopeDebug branch an existing scope with user-provided span name with debug log level.
   139  func BranchNamedScopeDebug(ctx context.Context, spanName string, opts ...trace.SpanStartOption) context.Context {
   140  	if !trace.SpanContextFromContext(ctx).IsValid() {
   141  		return ctx
   142  	}
   143  	return startNamedScope(ctx, spanName, DebugLevel, 1, opts...)
   144  }
   145  
   146  // BranchNamedScopeInfo branch an existing scope with user-provided span name with info log level.
   147  func BranchNamedScopeInfo(ctx context.Context, spanName string, opts ...trace.SpanStartOption) context.Context {
   148  	if !trace.SpanContextFromContext(ctx).IsValid() {
   149  		return ctx
   150  	}
   151  	return startNamedScope(ctx, spanName, InfoLevel, 1, opts...)
   152  }
   153  
   154  // BranchNamedScopeWarn branch an existing scope with user-provided span name with warn log level.
   155  func BranchNamedScopeWarn(ctx context.Context, spanName string, opts ...trace.SpanStartOption) context.Context {
   156  	if !trace.SpanContextFromContext(ctx).IsValid() {
   157  		return ctx
   158  	}
   159  	return startNamedScope(ctx, spanName, WarnLevel, 1, opts...)
   160  }
   161  
   162  // BranchNamedScopeError branch an existing scope with user-provided span name with error log level.
   163  func BranchNamedScopeError(ctx context.Context, spanName string, opts ...trace.SpanStartOption) context.Context {
   164  	if !trace.SpanContextFromContext(ctx).IsValid() {
   165  		return ctx
   166  	}
   167  	return startNamedScope(ctx, spanName, ErrorLevel, 1, opts...)
   168  }
   169  
   170  // EndScope end scope created by `StartScope()`/`StartNamedScope()`.
   171  // Logs error return value and ends span.
   172  func EndScope(ctx context.Context, err error) {
   173  	span := trace.SpanFromContext(ctx)
   174  
   175  	// If scope returns an error, mark span with error.
   176  	if err != nil {
   177  		span.RecordError(err)
   178  		span.SetStatus(codes.Error, err.Error())
   179  
   180  		if typedErr, ok := err.(*errors.TypedError); ok {
   181  			span.SetAttributes(
   182  				attribute.String(ErrorClassKey, typedErr.Class()),
   183  				attribute.String(ErrorTypeKey, typedErr.Type()),
   184  			)
   185  		}
   186  	}
   187  
   188  	span.End()
   189  }
   190  
   191  var (
   192  	// Deprecated: Use CallScope
   193  	Scope = CallScope
   194  	// Deprecated: Use CallScopeDebug
   195  	ScopeDebug = CallScopeDebug
   196  	// Deprecated: Use CallScopeInfo
   197  	ScopeInfo = CallScopeInfo
   198  	// Deprecated: Use CallScopeWarn
   199  	ScopeWarn = CallScopeWarn
   200  	// Deprecated: Use CallScopeError
   201  	ScopeError = CallScopeError
   202  	// Deprecated: Use CallScope
   203  	NamedScope = CallNamedScope
   204  	// Deprecated: Use CallNamedScopeDebug
   205  	NamedScopeDebug = CallNamedScopeDebug
   206  	// Deprecated: Use CallNamedScopeInfo
   207  	NamedScopeInfo = CallNamedScopeInfo
   208  	// Deprecated: Use CallNamedScopeWarn
   209  	NamedScopeWarn = CallNamedScopeWarn
   210  	// Deprecated: Use CallNamedScopeError
   211  	NamedScopeError = CallNamedScopeError
   212  )
   213  
   214  // CallScope calls action function within a tracing span named after the calling
   215  // function.
   216  // Equivalent to wrapping a code block with `StartScope()`/`EndScope()`.
   217  func CallScope(ctx context.Context, action ScopeAction, opts ...trace.SpanStartOption) error {
   218  	spanName, fileTag := getCallerSpanName(1)
   219  	ctx = startSpan(ctx, spanName, fileTag, InfoLevel, opts...)
   220  	err := action(ctx)
   221  	EndScope(ctx, err)
   222  	return err
   223  }
   224  
   225  // CallScopeDebug calls action function within a tracing span named after the calling
   226  // function.  Scope tagged with log level debug.
   227  // Equivalent to wrapping a code block with `StartScope()`/`EndScope()`.
   228  func CallScopeDebug(ctx context.Context, action ScopeAction, opts ...trace.SpanStartOption) error {
   229  	spanName, fileTag := getCallerSpanName(1)
   230  	ctx = startSpan(ctx, spanName, fileTag, DebugLevel, opts...)
   231  	err := action(ctx)
   232  	EndScope(ctx, err)
   233  	return err
   234  }
   235  
   236  // CallScopeInfo calls action function within a tracing span named after the calling
   237  // function.  Scope tagged with log level info.
   238  // Equivalent to wrapping a code block with `StartScope()`/`EndScope()`.
   239  func CallScopeInfo(ctx context.Context, action ScopeAction, opts ...trace.SpanStartOption) error {
   240  	spanName, fileTag := getCallerSpanName(1)
   241  	ctx = startSpan(ctx, spanName, fileTag, InfoLevel, opts...)
   242  	err := action(ctx)
   243  	EndScope(ctx, err)
   244  	return err
   245  }
   246  
   247  // CallScopeWarn calls action function within a tracing span named after the calling
   248  // function.  Scope tagged with log level warning.
   249  // Equivalent to wrapping a code block with `StartScope()`/`EndScope()`.
   250  func CallScopeWarn(ctx context.Context, action ScopeAction, opts ...trace.SpanStartOption) error {
   251  	spanName, fileTag := getCallerSpanName(1)
   252  	ctx = startSpan(ctx, spanName, fileTag, WarnLevel, opts...)
   253  	err := action(ctx)
   254  	EndScope(ctx, err)
   255  	return err
   256  }
   257  
   258  // CallScopeError calls action function within a tracing span named after the calling
   259  // function.  Scope tagged with log level error.
   260  // Equivalent to wrapping a code block with `StartScope()`/`EndScope()`.
   261  func CallScopeError(ctx context.Context, action ScopeAction, opts ...trace.SpanStartOption) error {
   262  	spanName, fileTag := getCallerSpanName(1)
   263  	ctx = startSpan(ctx, spanName, fileTag, ErrorLevel, opts...)
   264  	trace.SpanFromContext(ctx).SetAttributes(attribute.Bool("error", true))
   265  	err := action(ctx)
   266  	EndScope(ctx, err)
   267  	return err
   268  }
   269  
   270  // CallNamedScope calls action function within a tracing span.
   271  // Equivalent to wrapping a code block with `StartNamedScope()`/`EndScope()`.
   272  func CallNamedScope(ctx context.Context, spanName string, action ScopeAction, opts ...trace.SpanStartOption) error {
   273  	fileTag := getFileTag(1)
   274  	ctx = startSpan(ctx, spanName, fileTag, InfoLevel, opts...)
   275  	err := action(ctx)
   276  	EndScope(ctx, err)
   277  	return err
   278  }
   279  
   280  // CallNamedScopeDebug calls action function within a tracing span.  Scope tagged
   281  // with log level debug.
   282  // Equivalent to wrapping a code block with `StartNamedScope()`/`EndScope()`.
   283  func CallNamedScopeDebug(ctx context.Context, spanName string, action ScopeAction, opts ...trace.SpanStartOption) error {
   284  	fileTag := getFileTag(1)
   285  	ctx = startSpan(ctx, spanName, fileTag, DebugLevel, opts...)
   286  	err := action(ctx)
   287  	EndScope(ctx, err)
   288  	return err
   289  }
   290  
   291  // CallNamedScopeInfo calls action function within a tracing span.  Scope tagged
   292  // with log level info.
   293  // Equivalent to wrapping a code block with `StartNamedScope()`/`EndScope()`.
   294  func CallNamedScopeInfo(ctx context.Context, spanName string, action ScopeAction, opts ...trace.SpanStartOption) error {
   295  	fileTag := getFileTag(1)
   296  	ctx = startSpan(ctx, spanName, fileTag, InfoLevel, opts...)
   297  	err := action(ctx)
   298  	EndScope(ctx, err)
   299  	return err
   300  }
   301  
   302  // CallNamedScopeWarn calls action function within a tracing span.  Scope tagged
   303  // with log level warning.
   304  // Equivalent to wrapping a code block with `StartNamedScope()`/`EndScope()`.
   305  func CallNamedScopeWarn(ctx context.Context, spanName string, action ScopeAction, opts ...trace.SpanStartOption) error {
   306  	fileTag := getFileTag(1)
   307  	ctx = startSpan(ctx, spanName, fileTag, WarnLevel, opts...)
   308  	err := action(ctx)
   309  	EndScope(ctx, err)
   310  	return err
   311  }
   312  
   313  // CallNamedScopeError calls action function within a tracing span.  Scope tagged
   314  // with log level error.
   315  // Equivalent to wrapping a code block with `StartNamedScope()`/`EndScope()`.
   316  func CallNamedScopeError(ctx context.Context, spanName string, action ScopeAction, opts ...trace.SpanStartOption) error {
   317  	fileTag := getFileTag(1)
   318  	ctx = startSpan(ctx, spanName, fileTag, ErrorLevel, opts...)
   319  	trace.SpanFromContext(ctx).SetAttributes(attribute.Bool("error", true))
   320  	err := action(ctx)
   321  	EndScope(ctx, err)
   322  	return err
   323  }
   324  
   325  // CallScopeBranch calls action function within a tracing span named after the
   326  // calling function.
   327  // Equivalent to wrapping a code block with `BranchScope()`/`EndScope()`.
   328  func CallScopeBranch(ctx context.Context, action ScopeAction, opts ...trace.SpanStartOption) error {
   329  	if !trace.SpanContextFromContext(ctx).IsValid() {
   330  		return action(ctx)
   331  	}
   332  	spanName, fileTag := getCallerSpanName(1)
   333  	ctx = startSpan(ctx, spanName, fileTag, InfoLevel, opts...)
   334  	err := action(ctx)
   335  	EndScope(ctx, err)
   336  	return err
   337  }
   338  
   339  // CallScopeBranchDebug calls action function within a tracing span named after the
   340  // calling function.  Scope tagged with log level debug.
   341  // Equivalent to wrapping a code block with `BranchScopeDebug()`/`EndScope()`.
   342  func CallScopeBranchDebug(ctx context.Context, action ScopeAction, opts ...trace.SpanStartOption) error {
   343  	if !trace.SpanContextFromContext(ctx).IsValid() {
   344  		return action(ctx)
   345  	}
   346  	spanName, fileTag := getCallerSpanName(1)
   347  	ctx = startSpan(ctx, spanName, fileTag, DebugLevel, opts...)
   348  	err := action(ctx)
   349  	EndScope(ctx, err)
   350  	return err
   351  }
   352  
   353  // CallScopeBranchInfo calls action function within a tracing span named after the
   354  // calling function.  Scope tagged with log level info.
   355  // Equivalent to wrapping a code block with `BranchScopeInfo()`/`EndScope()`.
   356  func CallScopeBranchInfo(ctx context.Context, action ScopeAction, opts ...trace.SpanStartOption) error {
   357  	if !trace.SpanContextFromContext(ctx).IsValid() {
   358  		return action(ctx)
   359  	}
   360  	spanName, fileTag := getCallerSpanName(1)
   361  	ctx = startSpan(ctx, spanName, fileTag, InfoLevel, opts...)
   362  	err := action(ctx)
   363  	EndScope(ctx, err)
   364  	return err
   365  }
   366  
   367  // CallScopeBranchWarn calls action function within a tracing span named after the
   368  // calling function.  Scope tagged with log level warn.
   369  // Equivalent to wrapping a code block with `BranchScopeWarn()`/`EndScope()`.
   370  func CallScopeBranchWarn(ctx context.Context, action ScopeAction, opts ...trace.SpanStartOption) error {
   371  	if !trace.SpanContextFromContext(ctx).IsValid() {
   372  		return action(ctx)
   373  	}
   374  	spanName, fileTag := getCallerSpanName(1)
   375  	ctx = startSpan(ctx, spanName, fileTag, WarnLevel, opts...)
   376  	err := action(ctx)
   377  	EndScope(ctx, err)
   378  	return err
   379  }
   380  
   381  // CallScopeBranchError calls action function within a tracing span named after the
   382  // calling function.  Scope tagged with log level error.
   383  // Equivalent to wrapping a code block with `BranchScopeError()`/`EndScope()`.
   384  func CallScopeBranchError(ctx context.Context, action ScopeAction, opts ...trace.SpanStartOption) error {
   385  	if !trace.SpanContextFromContext(ctx).IsValid() {
   386  		return action(ctx)
   387  	}
   388  	spanName, fileTag := getCallerSpanName(1)
   389  	ctx = startSpan(ctx, spanName, fileTag, ErrorLevel, opts...)
   390  	err := action(ctx)
   391  	EndScope(ctx, err)
   392  	return err
   393  }
   394  
   395  // CallNamedScopeBranch calls action function within an existing tracing span.
   396  // Equivalent to wrapping a code block with `BranchNamedScope()`/`EndScope()`.
   397  func CallNamedScopeBranch(ctx context.Context, spanName string, action ScopeAction, opts ...trace.SpanStartOption) error {
   398  	if !trace.SpanContextFromContext(ctx).IsValid() {
   399  		return action(ctx)
   400  	}
   401  	fileTag := getFileTag(1)
   402  	ctx = startSpan(ctx, spanName, fileTag, InfoLevel, opts...)
   403  	err := action(ctx)
   404  	EndScope(ctx, err)
   405  	return err
   406  }
   407  
   408  // CallNamedScopeBranchDebug calls action function within an existing tracing span.
   409  // Scope tagged with log level debug.
   410  // Equivalent to wrapping a code block with `BranchNamedScope()`/`EndScope()`.
   411  func CallNamedScopeBranchDebug(ctx context.Context, spanName string, action ScopeAction, opts ...trace.SpanStartOption) error {
   412  	if !trace.SpanContextFromContext(ctx).IsValid() {
   413  		return action(ctx)
   414  	}
   415  	fileTag := getFileTag(1)
   416  	ctx = startSpan(ctx, spanName, fileTag, DebugLevel, opts...)
   417  	err := action(ctx)
   418  	EndScope(ctx, err)
   419  	return err
   420  }
   421  
   422  // CallNamedScopeBranchInfo calls action function within an existing tracing span.
   423  // Scope tagged with log level debug.
   424  // Equivalent to wrapping a code block with `BranchNamedScope()`/`EndScope()`.
   425  func CallNamedScopeBranchInfo(ctx context.Context, spanName string, action ScopeAction, opts ...trace.SpanStartOption) error {
   426  	if !trace.SpanContextFromContext(ctx).IsValid() {
   427  		return action(ctx)
   428  	}
   429  	fileTag := getFileTag(1)
   430  	ctx = startSpan(ctx, spanName, fileTag, InfoLevel, opts...)
   431  	err := action(ctx)
   432  	EndScope(ctx, err)
   433  	return err
   434  }
   435  
   436  // CallNamedScopeBranchWarn calls action function within an existing tracing span.
   437  // Scope tagged with log level debug.
   438  // Equivalent to wrapping a code block with `BranchNamedScope()`/`EndScope()`.
   439  func CallNamedScopeBranchWarn(ctx context.Context, spanName string, action ScopeAction, opts ...trace.SpanStartOption) error {
   440  	if !trace.SpanContextFromContext(ctx).IsValid() {
   441  		return action(ctx)
   442  	}
   443  	fileTag := getFileTag(1)
   444  	ctx = startSpan(ctx, spanName, fileTag, WarnLevel, opts...)
   445  	err := action(ctx)
   446  	EndScope(ctx, err)
   447  	return err
   448  }
   449  
   450  // CallNamedScopeBranchError calls action function within an existing tracing span.
   451  // Scope tagged with log level debug.
   452  // Equivalent to wrapping a code block with `BranchNamedScope()`/`EndScope()`.
   453  func CallNamedScopeBranchError(ctx context.Context, spanName string, action ScopeAction, opts ...trace.SpanStartOption) error {
   454  	if !trace.SpanContextFromContext(ctx).IsValid() {
   455  		return action(ctx)
   456  	}
   457  	fileTag := getFileTag(1)
   458  	ctx = startSpan(ctx, spanName, fileTag, ErrorLevel, opts...)
   459  	err := action(ctx)
   460  	EndScope(ctx, err)
   461  	return err
   462  }
   463  
   464  func startScope(ctx context.Context, level Level, skipCallers int, opts ...trace.SpanStartOption) context.Context {
   465  	if level <= ErrorLevel {
   466  		opts = append(opts, trace.WithAttributes(
   467  			attribute.Bool("error", true),
   468  		))
   469  	}
   470  
   471  	spanName, fileTag := getCallerSpanName(skipCallers + 1)
   472  	return startSpan(ctx, spanName, fileTag, level, opts...)
   473  }
   474  
   475  func startNamedScope(ctx context.Context, spanName string, level Level, skipCallers int, opts ...trace.SpanStartOption) context.Context {
   476  	if level <= ErrorLevel {
   477  		opts = append(opts, trace.WithAttributes(
   478  			attribute.Bool("error", true),
   479  		))
   480  	}
   481  
   482  	fileTag := getFileTag(skipCallers + 1)
   483  	return startSpan(ctx, spanName, fileTag, level, opts...)
   484  }
   485  
   486  func startSpan(ctx context.Context, spanName, fileTag string, level Level, opts ...trace.SpanStartOption) context.Context {
   487  	opts = append(opts, trace.WithAttributes(
   488  		attribute.String("file", fileTag),
   489  	))
   490  
   491  	// Embed log level parameter as context value.
   492  	ctx = context.WithValue(ctx, logLevelCtxKey, level)
   493  	ctx, _ = Tracer().Start(ctx, spanName, opts...)
   494  	return ctx
   495  }
   496  
   497  // getCallerSpanName returns function and file name:line of the caller.
   498  //
   499  // Use skip=0 to get the caller of getCallerSpanName.
   500  //
   501  // Use skip=1 to get the caller of the caller(a getCallerSpanName() wrapper) and so on.
   502  func getCallerSpanName(skip int) (spanName, fileTag string) {
   503  	pc, file, line, ok := runtime.Caller(skip + 1)
   504  
   505  	// Determine source file and line number.
   506  	if ok {
   507  		fileTag = file + ":" + strconv.Itoa(line)
   508  		spanName = runtime.FuncForPC(pc).Name()
   509  	} else {
   510  		// Rare condition.  Probably a bug in caller.
   511  		fileTag = "unknown"
   512  	}
   513  	return
   514  }
   515  
   516  // getFileTag returns file name:line of the caller.
   517  //
   518  // Use skip=0 to get the caller of getFileTag.
   519  //
   520  // Use skip=1 to get the caller of the caller(a getFileTag() wrapper).
   521  func getFileTag(skip int) string {
   522  	_, file, line, ok := runtime.Caller(skip + 1)
   523  
   524  	// Determine source file and line number.
   525  	if !ok {
   526  		// Rare condition.  Probably a bug in caller.
   527  		return "unknown"
   528  	}
   529  
   530  	return file + ":" + strconv.Itoa(line)
   531  }