API Guide
The example code in this document is available in the github.com/whatap/go-api-example repository.
Download
Download the package and the related dependencies.
go get github.com/whatap/go-api
Getting started
init, shutdown
Start the monitoring module by using the trace.Init()
function. defer trace.Shutdown()
makes sure that the monitoring ends.
import "github.com/whatap/go-api/trace"
func main(){
trace.Init(nil)
//It must be executed before closing the app.
defer trace.Shutdown()
...
}
func Init(m map[string]string)
This can be set in the initial stage of the application by declaring it in the form of map[string]string
. Otherwise, more settings can be added in whatap.conf. The performance information can be sent to the WhaTap collection server only when TCP connection to the agent is established normally. It performs TCP communication via 127.0.0.1:6600 using the basic connection information.
To change the connection information, pass the settings to the Init
function, or set them in the whatap.conf file and then restart the application.
m := make(map[string]string)
m["net_ipc_host"] = "127.0.0.1"
m["net_ipc_port"] = "6601"
trace.Init(m)
accesskey={access key}
whatap.server.host={collection server IP}
net_ipc_host=127.0.0.1
net_ipc_host=6600
Context
The agents collect performance data based on the transaction. The transactions are classified based on the context
to which whatap context(trace.TraceCtx)
belongs. The performance data that is not related to transactions is ignored or collected only as statistical data.
Generating transactions
The whatap context
is generated by the Start
and StartWithReqest
functions of the go-api/trace
module. Set the TraceCtx
data with the whatap
key in the context
.
var traceCtx *TraceCtx
traceCtx.Txid = keygen.Next()
ctx = context.WithValue(ctx, "whatap", traceCtx)
In APIs for transactions, SQLs, DBConnections, external HTTP requests, and general function tracing, the Context
in which the TraceCtx
object exists as the whatap
key is required at startup.
Transaction tracing
It traces all transactions from web requests to responses, and traces transactions for normal work units. It consists of the startup and end functions. It is recognized as a single transaction, and you can see the detailed views on the hitmap widget and the statistical metrics such as TPS, response time, and average response time.
You can collect HTTP parameters and HTTP headers through the settings.
Web transaction trace
http.HandleFunc("/index", func(w http.ResponseWriter, r *http.Request) {
ctx, _ := trace.StartWithRequest(r)
defer trace.End(ctx, nil)
}
-
trace.Func()
,trace.HandlerFunc()
-
Function that wraps Handle and HandFunc of the
net/http
-
Proceed with
trace.StartWithRequest
andtrace.End
in the same way. -
The name of the web transaction is set to RequestURI.
-
-
trace.Step()
It provides the function to output the strings delivered by the user to the profile data.
Gohttp.HandleFunc("/wrapHandleFunc", trace.Func(func(w http.ResponseWriter, r *http.Request) {
...
}))Gohttp.Handle("/wrapHandleFunc1", trace.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
...
}))
General transaction trace
func main() {
...
ctx := context.Background()
ctx, _ := trace.Start(ctx, "Custom Transaction")
...
trace.End(ctx, nil)
...
}
API
func Start(ctx context.Context, name string) (context.Context, error)
func End(ctx context.Context, err error) error
func StartWithRequest(r *http.Request) (context.Context, error)
func Step(ctx context.Context, title, message string, elapsed, value int) error
func HandlerFunc(handler func(http.ResponseWriter, *http.Request)) http.HandlerFunc
func Func(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request)
DB connection and SQL tracing
Execution times and errors can be collected by passing parameters for DB connection, SQL syntax, errors, and prepared statements to the API. SQL statements can be collected up to 32 KB. The parameters for SQL prepared statements are collected up to 20 and up to 256 bytes for each.
DB Connection
import (
whatapsql "github.com/whatap/go-api/sql"
)
func main(){
trace.Init(nil)
//It must be executed before closing the app.
defer trace.Shutdown()
ctx, _ := trace.Start(context.Background(), "Trace Open DB")
defer trace.End(ctx, nil)
sqlCtx, _ := whatapsql.StartOpen(ctx, "id@tcp(x.x.x.x:3306)/test")
db, err := sql.Open("mysql", "id:pwd@tcp(x.x.x.x:3306)/test")
whatapsql.End(sqlCtx, err)
defer db.Close()
}
import (
whatapsql "github.com/whatap/go-api/sql"
)
func main(){
trace.Init(nil)
//It must be executed before closing the app.
defer trace.Shutdown()
ctx, _ := trace.Start(context.Background(), "Trace Query")
defer trace.End(ctx, nil)
query = "select id, subject from tbl_faq limit 10"
sqlCtx, _ = whatapsql.Start(ctx, "id:pwd@tcp(x.x.x.x:3306)/test", query)
rows, err := db.QueryContext(ctx, query)
whatapsql.End(sqlCtx, err)
}
import (
whatapsql "github.com/whatap/go-api/sql"
)
func main(){
trace.Init(nil)
//It must be executed before closing the app.
defer trace.Shutdown()
ctx, _ := trace.Start(context.Background(), "Trace Prepared Statement")
defer trace.End(ctx, nil)
// Create Prepared Statement
query = "select id, subject from tbl_faq where id = ? limit ?"
stmt, err := db.Prepare(query)
if err != nil {
return
}
defer stmt.Close()
params := make([]interface{}, 0)
params = append(params, 8)
params = append(params, 1)
sqlCtx, _ := whatapsql.StartWithParamArray(ctx, "id:pwd(x.x.x.x:3306)/test", query, params)
rows, err := stmt.QueryContext(ctx, params...)
whatapsql.End(sqlCtx, err)
sqlCtx, _ = whatapsql.StartWithParam(ctx, "id:pwd(x.x.x.x:3306)/test", query, params...)
rows, err := stmt.QueryContext(ctx, params...)
whatapsql.End(sqlCtx, err)
}
Database/SQL package configuration
Use the whatapsql.OpenContext
function instead of the sql.Open
function of the database/sql package. It is recommended to use the functions that pass the contexts
, such as PrepareContext
, QueryContext
, and ExecContext
. The context
to be delivered must have the whatap TraceCtx data through trace.Start()
.
import (
_ "github.com/go-sql-driver/mysql"
"github.com/whatap/go-api/instrumentation/database/sql/whatapsql"
)
func main() {
config := make(map[string]string)
trace.Init(config)
defer trace.Shutdown()
// The whatap TraceCtx is generated inside whataphttp.Func.
http.HandleFunc("/query", whataphttp.Func(func(w http.ResponseWriter, r *http.Request){
ctx := r.Context()
// Use the WhaTap Driver. It passes the contexts that have TraceCtx.
db, err := whatapsql.OpenContext(ctx, "mysql", dataSource)
if err != nil {
fmt.Println("Error whatapsql.Open ", err)
return
}
defer db.Close()
...
query := "select id, subject from tbl_faq limit 10"
// It passes the contexts that have TraceCtx.
if rows, err := db.QueryContext(ctx, query); err == nil {
...
}
}
...
}
API
func Start(ctx context.Context, dbhost, sql string) (*SqlCtx, error)
func StartOpen(ctx context.Context, dbhost string) (*SqlCtx, error)
func End(sqlCtx *SqlCtx, err error) error
func StartWithParam(ctx context.Context, dbhost, sql, param ...interface{}) (*SqlCtx, error)
func StartWithParamArray(ctx context.Context, dbhost, sql string, param []interface{}) (*SqlCtx, error)
func Trace(ctx context.Context, dbhost, sql, param string, elapsed int, err error) error
Tracing the HTTP requests
import (
"github.com/whatap/go-api/httc"
)
func main(){
trace.Init(nil)
//It must be executed before closing the app.
defer trace.Shutdown()
ctx, _ := trace.Start(context.Background(), "Trace Http Call")
defer trace.End(ctx, nil)
httpcCtx, _ := httpc.Start(ctx, callUrl)
resp, err := http.Get(callUrl)
if err == nil {
httpc.End(httpcCtx, resp.StatusCode, "", nil)
} else {
httpc.End(httpcCtx, 0, "", err)
}
}
http transport RoundTrip
You can set the RoundTrip.
import (
"github.com/whatap/go-api/instrumentation/net/http/whataphttp"
)
func main() {
config := make(map[string]string)
trace.Init(config)
defer trace.Shutdown()
ctx, _ := trace.Start(context.Background(), "Http call")
defer trace.End(ctx, nil)
callUrl := "http://aaa.com/xxx"
client := http.DefaultClient
// Use WhaTap RoundTrip. Passes the context with whatap's TraceCtx.
client.Transport = whataphttp.NewRoundTrip(ctx, http.DefaultTransport)
resp, err := client.Get(callUrl)
if err != nil {
...
}
defer resp.Body.Close()
...
}
API
func Start(ctx context.Context, url string) (*HttpcCtx, error)
func End(httpcCtx *HttpcCtx, status int, reason string, err error) error
func Trace(ctx context.Context, host string, port int, url string, elapsed int, status int, reason string, err error) error
Tracing multi-transactions (distributed tracing)
Multi-transaction means the transaction involving a different agent or project. Multiple Transaction Trace is to trace calls between the application services registered in the WhaTap project.
The Go agent traces multiple transactions with three HTTP header keys (x-wtap-po
, x-wtap-mst
, x-wtap-sp1
). If HTTP transactions passing through the gateway are not being correlated for tracing, check the HTTP header conditions.
It supports TraceContext for open source tracing. (traceparent header tracing)
Configuring the agent
To enable multi-transaction tracing, set the following options in the agent configuration file (whatap.conf).
# Activation option, Default: false
mtrace_enabled=true
# Sampling rate, Default: 10
mtrace_rate=100
Passing the Request Header
To check the distributed tracing information (header), pass the Request Header.
// import "github.com/whatap/go-api/trace"
func UpdateMtrace(traceCtx *trace.TraceCtx, header http.Header)
The trace.StartWithRequest
function internally calls the UpdateMtrace
function. If you do not use the trace.StartWithRequest
function, you have to directly call it and then analyze the passed header information.
// func GetTraceContext(ctx context.Context) (context.Context, *TraceCtx)
// if v := ctx.Value("whatap"); v != nil {
// return ctx, v.(*TraceCtx)
// }
ctx, traceCtx := trace.GetTraceContext(ctx);
if traceCtx != nil {
trace.UpdateMtrace(traceCtx, header)
}
See the code inside the trace.Start
function:
func Start(ctx context.Context, name string) (context.Context, error) {
ctx, traceCtx := NewTraceContext(ctx)
traceCtx.Name = name
traceCtx.StartTime = dateutil.SystemNow()
// update multi trace info
UpdateMtrace(traceCtx, http.Header{})
...
See the code inside the trace.StartWithRequest
function:
func StartWithRequest(r *http.Request) (context.Context, error) {
ctx, traceCtx := NewTraceContext(r.Context())
traceCtx.Name = r.RequestURI
traceCtx.StartTime = dateutil.SystemNow()
// update multi trace info
UpdateMtrace(traceCtx, r.Header)
...
Adding the header information when accessing the HTTP
When making an outbound HTTP connection, add the header information in the Request Header for tracing.
import (
"github.com/whatap/go-api/trace"
)
func GetMTrace(ctx context.Context) http.Header
The next is the function to retrieve the headers required for distributed tracing. You have to add the returned headers to the external request. The context must contain trace.TraceContext
for the 'whatap' entry. The information is added internally as whatap context information when using the trace.Start
, StartWithRequest
, and StartWithContext
functions.
headers := trace.GetMTrace(wCtx)
for key, _ := range headers {
// req *http.Request
// The Add function is also available.
req.Header.Set(key, headers.Get(key))
}
Adding the header automatically
The WhaTap transport (RoundTrip) already has the code that uses GetMTrace
internally. You can automatically add header information by simply enabling its option.
import (
"net/http"
"github.com/whatap/go-api/instrumentation/net/http/whataphttp"
)
...
//client := http.DefaultClient
client := http.Client{
Timeout: timeout,
}
client.Transport = whataphttp.NewRoundTrip(ctx, http.DefaultTransport)
if resp, err := client.Get(callUrl); err == nil {
...
}
The following lists some usage examples of Transport: The context must contain trace.TraceContext
for the 'whatap' entry.
import (
"net/http"
"github.com/whatap/go-api/instrumentation/net/http/whataphttp"
)
...
client := http.DefaultClient
client.Transport = NewAccessLogRoundTrip(whataphttp.NewRoundTrip(ctx, http.DefaultTransport))
...
Function tracing
This API measures the execution time of the user function or desired section. API can be set before or after execution of the function.
import (
"github.com/whatap/go-api/method"
)
func main(){
trace.Init(nil)
//It must be executed before closing the app.
defer trace.Shutdown()
ctx, _ := trace.Start(context.Background(), "Trace Method")
defer trace.End(ctx, nil)
getUser(ctx)
}
func getUser(ctx context.Context) {
methodCtx, _ := method.Start(ctx, "getUser")
defer method.End(methodCtx, nil)
time.Sleep(time.Duration(1) * time.Second)
}
API
func Start(ctx context.Context, name string) (*MethodCtx, error)
func End(methodCtx *MethodCtx, err error) error
func Trace(ctx context.Context, name string, elapsed int, err error) error
Log
Configuring the agent
Set agent options in the whatap.conf file before running the application.
# Enable all log collection
logsink_enabled=true
# Enable stdout collection
logsink_stdout_enabled=true
# Enable stderr collection
logsink_stderr_enabled=true
# Optional. This is a setting for compressing data.
logsin_zip_enabled=true
Before initializing the logging library
You must call the trace.Init()
function before initializing the logging library. It internally wraps os.Stdout
and os.Stderr
. Afterwards, the log library automatically collects logs when os.Stdout
and os.Stderr
have been set.
After initializing the logging library
After initializing the logging library, you can set the io.Writer wrapped through a separate configuration function.
-
logsink.GetWriterHookStdout()
: It returns io.Writer that has wrappedos.Stderr
. It collects the log by using the WhaTap log while outputting toos.Stdout
. -
logsink.GetWriterHookStderr()
: It returns io.Writer that has wrappedos.Stderr
. The wrapped io.Writer outputs toos.Stderr
and collects the log contents by using the WhaTap log.
API
-
It returns io.Writer that has wrapped
os.Stdout
.func GetWriterHookStdout() io.Writer
-
It returns io.Writer that has wrapped
os.Stderr
.func GetWriterHookStderr() io.Writer
See the following package-specific code example:
log package
The log package sets os.Stderr
at the import time (in the init
function). Because the trace.Init
function cannot be called first, set the wrapped io.Writer (os.Stderr
) through the log.SetOutput
function.
The output using the print
function of the log package used afterwards maintains in os.Stderr
through the wrapped io.Writer, while collecting the log contents at the same time by using the WhaTap log.
import (
"log"
"github.com/whatap/go-api/logsink"
)
...
if logsink.GetWriterHookStderr() != nil {
// set single writer
log.SetOutput(logsink.GetWriterHookStderr())
// set multi writer
multi := io.MultiWriter(file, logsink.GetWriterFromStderr())
log.SetOutput(logsink.GetWriterHookStderr())
}
//
log.Println("aaaaa")
...
go.uber.org/zap
If you call the trace.Init()
function before setting os.Stdout
, logs are collected automatically. It collects logs by using the WhaTap log while outputting to os.Stdout
.
import (
"github.com/whatap/go-api/trace"
"github.com/whatap/go-api/logsink"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func main() {
trace.Init(nil)
//It must be executed before closing the app.
defer trace.Shutdown()
// fmt.Println("Logger stdout=", os.Stdout, zapcore.AddSync(os.Stdout))
consoleCore := zapcore.NewCore(
zapcore.NewConsoleEncoder(consoleEncoderConfig),
zapcore.AddSync(os.Stdout),
zap.InfoLevel,
)
// Menggabungkan core file dan console
core := zapcore.NewTee(consoleCore)
Log = zap.New(core, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel))
Log.Info("logger started")
...
}
github.com/sirupsen/logrus
The wrapped io.Writer outputs to os.Stderr
and collects the log contents by using the WhaTap log.
package main
import (
"time"
log "github.com/sirupsen/logrus"
"github.com/whatap/go-api/logsink"
"github.com/whatap/go-api/trace"
)
func main() {
trace.Init(nil)
defer trace.Shutdown()
//In WhaTap, set io.Writer wrapping os.Stderr as the output of logrus
if logsink.GetWriterHookStderr() != nil {
log.SetOutput(logsink.GetWriterHookStderr())
}
for i := 0; i < 100; i++ {
log.WithFields(log.Fields{
"animal": "tiger",
"habitat": "mountain",
}).Info("Index:[", i, "] A tiger appears")
time.Sleep(100 * time.Millisecond)
}
}