Skip to main content

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.

Go
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.

Go
m := make(map[string]string)
m["net_ipc_host"] = "127.0.0.1"
m["net_ipc_port"] = "6601"

trace.Init(m)
whatap.conf
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.

Go
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

Go
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 and trace.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.

    Go
    http.HandleFunc("/wrapHandleFunc", trace.Func(func(w http.ResponseWriter, r *http.Request) {
    ...
    }))
    Go
    http.Handle("/wrapHandleFunc1", trace.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    ...
    }))

General transaction trace

Go
func main() {
...

ctx := context.Background()
ctx, _ := trace.Start(ctx, "Custom Transaction")

...

trace.End(ctx, nil)

...
}

API

Go
func Start(ctx context.Context, name string) (context.Context, error)
Go
func End(ctx context.Context, err error) error
Go
func StartWithRequest(r *http.Request) (context.Context, error)
Go
func Step(ctx context.Context, title, message string, elapsed, value int) error
Go
func HandlerFunc(handler func(http.ResponseWriter, *http.Request)) http.HandlerFunc
Go
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

Go
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()
}
Go
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)
}
Go
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().

Go

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

Go
func Start(ctx context.Context, dbhost, sql string) (*SqlCtx, error)
Go
func StartOpen(ctx context.Context, dbhost string) (*SqlCtx, error)
Go
func End(sqlCtx *SqlCtx, err error) error
Go
func StartWithParam(ctx context.Context, dbhost, sql, param ...interface{}) (*SqlCtx, error)
Go
func StartWithParamArray(ctx context.Context, dbhost, sql string, param []interface{}) (*SqlCtx, error)
Go
func Trace(ctx context.Context, dbhost, sql, param string, elapsed int, err error) error

Tracing the HTTP requests

Go
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.

Go
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

Go
func Start(ctx context.Context, url string) (*HttpcCtx, error)
Go
func End(httpcCtx *HttpcCtx, status int, reason string, err error) error
Go
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.

Note

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).

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.

Go
// 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.

Go
// 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)
}
Note

See the code inside the trace.Start function:

Go
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:

Go
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.

Go
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.

Example code
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.

Example code
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.

Exmple code
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.

Go
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

Go
func Start(ctx context.Context, name string) (*MethodCtx, error)
Go
func End(methodCtx *MethodCtx, err error) error
Go
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.

whatap.conf
# 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 wrapped os.Stderr. It collects the log by using the WhaTap log while outputting to os.Stdout.

  • logsink.GetWriterHookStderr(): It returns io.Writer that has wrapped os.Stderr. The wrapped io.Writer outputs to os.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.

log package
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.

go.uber.org/zap
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.

github.com/sirupsen/logrus
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)
}
}