blob: a5d3d91fc328a6bb2fd64cedeb50a5cf0d21fc02 [file] [log] [blame]
Cosmos Nicolaou727ce702015-05-12 15:47:48 -07001// Copyright 2015 The Vanadium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
Cosmos Nicolaou4c53aed2015-05-19 22:13:03 -07005// Package apilog provides functions to be used in conjunction with logcop.
6// In particular, logcop will inject calls to these functions as the first
7// statement in methods that implement the v23 API. The output can
8// be controlled by vlog verbosity or vtrace.
Cosmos Nicolaou92c57482015-05-22 13:29:15 -07009// --vmodule=apilog=<level> can be used to globally control logging,
10// and --vmodule=module=<level> can also be used to control logging
11// on a per-file basis.
Cosmos Nicolaou727ce702015-05-12 15:47:48 -070012package apilog
13
14import (
15 "fmt"
16 "path"
17 "reflect"
18 "runtime"
19 "sync/atomic"
20
Cosmos Nicolaou727ce702015-05-12 15:47:48 -070021 "v.io/v23/context"
Cosmos Nicolaou0e4e3922015-06-10 16:30:09 -070022 "v.io/v23/logging"
Cosmos Nicolaou9691e5d2015-06-17 12:24:35 -070023
24 "v.io/x/ref/internal/logger"
Cosmos Nicolaou727ce702015-05-12 15:47:48 -070025)
26
27// logCallLogLevel is the log level beyond which calls are logged.
28const logCallLogLevel = 1
29
Cosmos Nicolaou4c53aed2015-05-19 22:13:03 -070030func callerLocation() string {
Cosmos Nicolaou727ce702015-05-12 15:47:48 -070031 var funcName string
32 const stackSkip = 1
Cosmos Nicolaou86e39602015-06-04 16:04:13 -070033 pc, _, _, ok := runtime.Caller(stackSkip + 1)
Cosmos Nicolaou727ce702015-05-12 15:47:48 -070034 if ok {
35 function := runtime.FuncForPC(pc)
36 if function != nil {
37 funcName = path.Base(function.Name())
38 }
39 }
Cosmos Nicolaou86e39602015-06-04 16:04:13 -070040 return funcName
Cosmos Nicolaou727ce702015-05-12 15:47:48 -070041}
42
Cosmos Nicolaou9691e5d2015-06-17 12:24:35 -070043func getLogger(ctx *context.T) logging.Logger {
44 if ctx == nil {
45 return logger.Global()
46 }
47 return ctx
48}
49
Cosmos Nicolaou727ce702015-05-12 15:47:48 -070050// LogCall logs that its caller has been called given the arguments
51// passed to it. It returns a function that is supposed to be called
52// when the caller returns, logging the caller’s return along with the
53// arguments it is provided with.
54// File name and line number of the call site and a randomly generated
55// invocation identifier is logged automatically. The path through which
56// the caller function returns will be logged automatically too.
57//
58// The canonical way to use LogCall is along the lines of the following:
59//
60// func Function(ctx *context.T, a Type1, b Type2) ReturnType {
61// defer apilog.LogCall(ctx, a, b)(ctx)
62// // ... function body ...
63// return retVal
64// }
65//
66// To log the return value as the function returns, the following
67// pattern should be used. Note that pointers to the output
68// variables should be passed to the returning function, not the
69// variables themselves. Also note that nil can be used when a context.T
70// is not available:
71//
72// func Function(a Type1, b Type2) (r ReturnType) {
73// defer apilog.LogCall(nil, a, b)(nil, &r)
74// // ... function body ...
75// return computeReturnValue()
76// }
77//
78// Note that when using this pattern, you do not need to actually
79// assign anything to the named return variable explicitly. A regular
80// return statement would automatically do the proper return variable
81// assignments.
82//
83// The log injector tool will automatically insert a LogCall invocation
84// into all implementations of the public API it runs, unless a Valid
85// Log Construct is found. A Valid Log Construct is defined as one of
86// the following at the beginning of the function body (i.e. should not
87// be preceded by any non-whitespace or non-comment tokens):
88// 1. defer apilog.LogCall(optional arguments)(optional pointers to return values)
89// 2. defer apilog.LogCallf(argsFormat, optional arguments)(returnValuesFormat, optional pointers to return values)
90// 3. // nologcall
91//
92// The comment "// nologcall" serves as a hint to log injection and
93// checking tools to exclude the function from their consideration.
94// It is used as follows:
95//
96// func FunctionWithoutLogging(args ...interface{}) {
97// // nologcall
98// // ... function body ...
99// }
100//
101func LogCall(ctx *context.T, v ...interface{}) func(*context.T, ...interface{}) {
Cosmos Nicolaou9691e5d2015-06-17 12:24:35 -0700102 l := getLogger(ctx)
103 if !l.V(logCallLogLevel) { // TODO(mattr): add call to vtrace.
Cosmos Nicolaou727ce702015-05-12 15:47:48 -0700104 return func(*context.T, ...interface{}) {}
105 }
Cosmos Nicolaou4c53aed2015-05-19 22:13:03 -0700106 callerLocation := callerLocation()
Cosmos Nicolaou727ce702015-05-12 15:47:48 -0700107 invocationId := newInvocationIdentifier()
108 var output string
109 if len(v) > 0 {
Cosmos Nicolaou4c53aed2015-05-19 22:13:03 -0700110 output = fmt.Sprintf("call[%s %s]: args:%v", callerLocation, invocationId, v)
Cosmos Nicolaou727ce702015-05-12 15:47:48 -0700111 } else {
Cosmos Nicolaou4c53aed2015-05-19 22:13:03 -0700112 output = fmt.Sprintf("call[%s %s]", callerLocation, invocationId)
Cosmos Nicolaou727ce702015-05-12 15:47:48 -0700113 }
Cosmos Nicolaou9691e5d2015-06-17 12:24:35 -0700114 l.InfoDepth(1, output)
Cosmos Nicolaou4c53aed2015-05-19 22:13:03 -0700115
Cosmos Nicolaou727ce702015-05-12 15:47:48 -0700116 // TODO(mattr): annotate vtrace span.
117 return func(ctx *context.T, v ...interface{}) {
118 var output string
119 if len(v) > 0 {
Cosmos Nicolaou4c53aed2015-05-19 22:13:03 -0700120 output = fmt.Sprintf("return[%s %s]: %v", callerLocation, invocationId, derefSlice(v))
Cosmos Nicolaou727ce702015-05-12 15:47:48 -0700121 } else {
Cosmos Nicolaou4c53aed2015-05-19 22:13:03 -0700122 output = fmt.Sprintf("return[%s %s]", callerLocation, invocationId)
Cosmos Nicolaou727ce702015-05-12 15:47:48 -0700123 }
Cosmos Nicolaou9691e5d2015-06-17 12:24:35 -0700124 getLogger(ctx).InfoDepth(1, output)
Cosmos Nicolaou727ce702015-05-12 15:47:48 -0700125 // TODO(mattr): annotate vtrace span.
126 }
127}
128
129// LogCallf behaves identically to LogCall, except it lets the caller to
130// customize the log messages via format specifiers, like the following:
131//
132// func Function(a Type1, b Type2) (r, t ReturnType) {
133// defer apilog.LogCallf(nil, "a: %v, b: %v", a, b)(nil, "(r,t)=(%v,%v)", &r, &t)
134// // ... function body ...
135// return finalR, finalT
136// }
137//
138func LogCallf(ctx *context.T, format string, v ...interface{}) func(*context.T, string, ...interface{}) {
Cosmos Nicolaou9691e5d2015-06-17 12:24:35 -0700139 l := getLogger(ctx)
140 if !l.V(logCallLogLevel) { // TODO(mattr): add call to vtrace.
Cosmos Nicolaou727ce702015-05-12 15:47:48 -0700141 return func(*context.T, string, ...interface{}) {}
142 }
Cosmos Nicolaou4c53aed2015-05-19 22:13:03 -0700143 callerLocation := callerLocation()
Cosmos Nicolaou727ce702015-05-12 15:47:48 -0700144 invocationId := newInvocationIdentifier()
Cosmos Nicolaou4c53aed2015-05-19 22:13:03 -0700145 output := fmt.Sprintf("call[%s %s]: %s", callerLocation, invocationId, fmt.Sprintf(format, v...))
Cosmos Nicolaou9691e5d2015-06-17 12:24:35 -0700146 l.InfoDepth(1, output)
Cosmos Nicolaou727ce702015-05-12 15:47:48 -0700147 // TODO(mattr): annotate vtrace span.
148 return func(ctx *context.T, format string, v ...interface{}) {
Cosmos Nicolaou4c53aed2015-05-19 22:13:03 -0700149 output := fmt.Sprintf("return[%s %s]: %v", callerLocation, invocationId, fmt.Sprintf(format, derefSlice(v)...))
Cosmos Nicolaou9691e5d2015-06-17 12:24:35 -0700150 getLogger(ctx).InfoDepth(1, output)
Cosmos Nicolaou727ce702015-05-12 15:47:48 -0700151 // TODO(mattr): annotate vtrace span.
152 }
153}
154
155func derefSlice(slice []interface{}) []interface{} {
156 o := make([]interface{}, 0, len(slice))
157 for _, x := range slice {
158 o = append(o, reflect.Indirect(reflect.ValueOf(x)).Interface())
159 }
160 return o
161}
162
163var invocationCounter uint64 = 0
164
165// newInvocationIdentifier generates a unique identifier for a method invocation
166// to make it easier to match up log lines for the entry and exit of a function
167// when looking at a log transcript.
168func newInvocationIdentifier() string {
169 const (
170 charSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"
171 charSetLen = uint64(len(charSet))
172 )
173 r := []byte{'@'}
174 for n := atomic.AddUint64(&invocationCounter, 1); n > 0; n /= charSetLen {
175 r = append(r, charSet[n%charSetLen])
176 }
177 return string(r)
178}