수동 적용 가이드
Go 라이브러리를 이용해 와탭 에이전트에 모니터링할 데이터를 수동으로 전송할 수 있습니다. 예제 코드는 github.com/whatap/go-api-example에서 확인할 수 있습니다.
자동 적용 vs 수동 적용
- 자동 적용 (권장): 자동 적용 가이드를 참조하여 코드 자동 삽입
- 수동 적용: 이 문서에서 설명하는 API를 직접 코드에 추가
시작하기
다음 명령어로 Go API를 설치하세요.
go get github.com/whatap/go-api@latest
Init, Shutdown
모니터링 모듈을 초기화하고 종료하는 기본 구조:
import "github.com/whatap/go-api/trace"
func main(){
trace.Init(nil)
defer trace.Shutdown()
...
}
Init 함수의 설정 옵션:
map[string]string형식으로 초기 설정 가능whatap.conf파일에서도 설정 가능- 기본 TCP 연결: 127.0.0.1:6600
m := make(map[string]string)
m["net_ipc_host"] = "127.0.0.1"
m["net_ipc_port"] = "6601"
trace.Init(m)
whatap.conf 설정:
accesskey={액세스 키}
whatap.server.host={수집 서버 IP 주소}
net_ipc_host=127.0.0.1
net_ipc_port=6600
Context 관리
에이전트는 whatap context(trace.TraceCtx)를 기준으로 트랜잭션을 구분합니다. 트랜잭션 외의 성능 정보는 무시되거나 통계만 수집됩니다.
트랜잭션 생성
var traceCtx *TraceCtx
traceCtx.Txid = keygen.Next()
ctx = context.WithValue(ctx, "whatap", traceCtx)
트랜잭션 추적
웹 트랜잭션 추적
http.HandleFunc("/index", func(w http.ResponseWriter, r *http.Request) {
ctx, _ := trace.StartWithRequest(r)
defer trace.End(ctx, nil)
})
래핑 함수:
trace.Func(): RequestURI를 트랜잭션 이름으로 설정trace.HandlerFunc(): 동일 기능 제공
일반 트랜잭션 추적
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 연결 및 SQL 추적
제한사항:
- SQL 구문: 최대 32KB
- Prepared 파라미터: 최대 20개, 각 256byte
DB Connection 추적
import whatapsql "github.com/whatap/go-api/sql"
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()
SQL Query 추적
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)
Prepared Statement 추적
query = "select id, subject from tbl_faq where id = ? limit ?"
stmt, err := db.Prepare(query)
defer stmt.Close()
params := make([]interface{}, 0)
params = append(params, 8)
params = append(params, 1)
sqlCtx, _ := whatapsql.StartWithParamArray(ctx, "id:pwd@tcp(x.x.x.x:3306)/test", query, params)
rows, err := stmt.QueryContext(ctx, params...)
whatapsql.End(sqlCtx, err)
database/sql 패키지 활용
import (
_ "github.com/go-sql-driver/mysql"
"github.com/whatap/go-api/instrumentation/database/sql/whatapsql"
)
db, err := whatapsql.OpenContext(ctx, "mysql", dataSource)
defer db.Close()
if rows, err := db.QueryContext(ctx, query); err == nil {
...
}
SQL 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
HTTP 요청 추적
import "github.com/whatap/go-api/httc"
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
import "github.com/whatap/go-api/instrumentation/net/http/whataphttp"
ctx, _ := trace.Start(context.Background(), "Http call")
defer trace.End(ctx, nil)
client := http.DefaultClient
client.Transport = whataphttp.NewRoundTrip(ctx, http.DefaultTransport)
resp, err := client.Get(callUrl)
defer resp.Body.Close()
HTTP 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
멀티 트랜잭션 추적 (분산 추적)
다른 에이전트나 프로젝트와 연관된 트랜잭션을 추적합니다.
헤더 키값:
x-wtap-pox-wtap-mstx-wtap-sp1
또한 OpenTrace의 traceparent 헤더도 지원합니다.
에이전트 설정
mtrace_enabled=true
mtrace_rate=100
Request Header 처리
func UpdateMtrace(traceCtx *trace.TraceCtx, header http.Header)
trace.StartWithRequest는 내부적으로 이 함수를 호출합니다.
ctx, traceCtx := trace.GetTraceContext(ctx)
if traceCtx != nil {
trace.UpdateMtrace(traceCtx, header)
}
Header 정보 조회 및 추가
func GetMTrace(ctx context.Context) http.Header
이 함수는 분산 추적에 필요한 Header를 반환합니다:
headers := trace.GetMTrace(wCtx)
for key, _ := range headers {
req.Header.Set(key, headers.Get(key))
}
자동 Header 추가
WhaTap transport에 이미 포함되어 있습니다:
client := http.Client{Timeout: timeout}
client.Transport = whataphttp.NewRoundTrip(ctx, http.DefaultTransport)
resp, err := client.Get(callUrl)
다중 Transport 사용:
client.Transport = NewAccessLogRoundTrip(whataphttp.NewRoundTrip(ctx, http.DefaultTransport))
함수 추적
사용자 함수 또는 특정 구간의 실행 시간 측정:
import "github.com/whatap/go-api/method"
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
Wrap 함수 (Go 1.18+ Generics)
Wrap 함수들은 클로저를 사용하여 계측 코드를 간결하게 작성할 수 있게 해줍니다. Go 1.18 이상의 Generics를 활용합니다.
Trace Wrap 함수 (범용)
특정 도메인에 속하지 않는 범용 추적에 사용합니다.
import "github.com/whatap/go-api/trace"
// Wrap - (T, error) 반환하는 범용 함수
result, err := trace.Wrap(ctx, "ThirdParty.Calculate", func() (int, error) {
return thirdPartyLib.Calculate(input)
})
// WrapError - error만 반환
err := trace.WrapError(ctx, "FileProcessor.Process", func() error {
return processFile(path)
})
// WrapVoid - 반환값 없음
trace.WrapVoid(ctx, "Cleanup.Execute", func() {
cleanup()
})
SQL Wrap 함수
Hook을 지원하지 않는 DB 라이브러리 (Aerospike, 일부 NoSQL 등) 계측에 유용합니다.
import "github.com/whatap/go-api/sql"
// Wrap - (T, error) 반환하는 쿼리
record, err := sql.Wrap(ctx, "aerospike://host:3000", "GET ns/set", func() (*aero.Record, error) {
return client.Get(policy, key)
})
// WrapError - error만 반환하는 쿼리 (INSERT, UPDATE, DELETE)
err := sql.WrapError(ctx, "aerospike://host:3000", "PUT ns/set", func() error {
return client.Put(policy, key, bins)
})
// WrapP - 파라미터 추적 포함
rows, err := sql.WrapP(ctx, "mysql://host:3306", "SELECT * FROM users WHERE id = ?",
[]interface{}{userId},
func() (*sql.Rows, error) {
return db.Query("SELECT * FROM users WHERE id = ?", userId)
},
)
// WrapOpen - DB 연결 추적
db, err := sql.WrapOpen(ctx, "mysql://host:3306", func() (*sql.DB, error) {
return sql.Open("mysql", dsn)
})
HTTP Wrap 함수
외부 HTTP API 호출 계측에 사용합니다.
import "github.com/whatap/go-api/httpc"
// Wrap - (T, error) 반환
resp, err := httpc.Wrap(ctx, "https://api.payment.com/charge", func() (*PaymentResp, error) {
return paymentClient.Charge(amount)
})
// WrapError - error만 반환
err := httpc.WrapError(ctx, "https://api.example.com/webhook", func() error {
return sendWebhook(payload)
})
// WrapWithStatus - HTTP 상태 코드 포함
resp, status, err := httpc.WrapWithStatus(ctx, "https://api.example.com/data",
func() (*Response, int, error) {
resp, err := client.Get(url)
if resp != nil {
return resp, resp.StatusCode, err
}
return nil, 0, err
},
)
Method Wrap 함수
사용자 정의 메서드/함수 추적에 사용합니다.
import "github.com/whatap/go-api/method"
// Wrap - (T, error) 반환
order, err := method.Wrap(ctx, "OrderService.ProcessOrder", func() (*Order, error) {
return s.processOrderInternal(orderID)
})
// WrapError - error만 반환
err := method.WrapError(ctx, "OrderService.ValidateOrder", func() error {
return s.validateOrder(order)
})
// WrapVoid - 반환값 없음
method.WrapVoid(ctx, "CacheService.Invalidate", func() {
s.cache.Delete(key)
})
Wrap 함수 API 요약
| 패키지 | 함수 | 설명 |
|---|---|---|
trace | Wrap[T] | 범용 (T, error) 반환 |
trace | WrapError | 범용 error만 반환 |
trace | WrapVoid | 범용 반환값 없음 |
sql | Wrap[T] | (T, error) 반환 쿼리 |
sql | WrapError | error만 반환 쿼리 |
sql | WrapP[T] | 파라미터 추적 포함 |
sql | WrapErrorP | 파라미터 + error만 |
sql | WrapOpen[T] | DB 연결 추적 |
httpc | Wrap[T] | HTTP 호출 |
httpc | WrapError | error만 반환 |
httpc | WrapWithStatus[T] | 상태 코드 포함 |
method | Wrap[T] | 메서드 추적 |
method | WrapError | error만 반환 |
method | WrapVoid | 반환값 없음 |
로그 수집
에이전트 설정
logsink_enabled=true # 로그 수집 활성화 (기본: false)
logsink_trace_enabled=true # 트랜잭션 연계 (기본: true)
logsink_zip_enabled=true # 압축 전송 (기본: true)
TraceLogWriter (권장)
logsink.GetTraceLogWriter()를 사용하면 로그와 트랜잭션을 연계할 수 있습니다. 로그에 @txid, @mtid, @gid 필드가 자동으로 추가됩니다.
func GetTraceLogWriter(w io.Writer) io.Writer
func GetTraceLogWriterWithCategory(w io.Writer, category string) io.Writer
변경 안내: 기존
GetWriterHookStdout(),GetWriterHookStderr()대신GetTraceLogWriter(os.Stdout),GetTraceLogWriter(os.Stderr)사용을 권장합니다.
기존 API (호환성 유지)
func GetWriterHookStdout() io.Writer // → GetTraceLogWriter(os.Stdout) 권장
func GetWriterHookStderr() io.Writer // → GetTraceLogWriter(os.Stderr) 권장
log package 예제
import (
"log"
"os"
"github.com/whatap/go-api/logsink"
"github.com/whatap/go-api/trace"
)
func main() {
trace.Init(nil)
defer trace.Shutdown()
// 권장: TraceLogWriter 사용
log.SetOutput(logsink.GetTraceLogWriter(os.Stderr))
// 기존 방식 (호환성 유지)
// log.SetOutput(logsink.GetWriterHookStderr())
log.Println("Application started")
}
go.uber.org/zap 예제
import (
"os"
"github.com/whatap/go-api/logsink"
"github.com/whatap/go-api/trace"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func main() {
trace.Init(nil)
defer trace.Shutdown()
// TraceLogWriter로 래핑
writer := logsink.GetTraceLogWriter(os.Stdout)
consoleCore := zapcore.NewCore(
zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()),
zapcore.AddSync(writer),
zap.InfoLevel,
)
logger := zap.New(consoleCore)
logger.Info("logger started")
}
sirupsen/logrus 예제
import (
"os"
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()
// TraceLogWriter로 래핑
log.SetOutput(logsink.GetTraceLogWriter(os.Stderr))
log.Info("Log message")
}