수동 적용 가이드
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")
}
웹 프레임워크 계측
instrumentation 패키지를 사용하면 주요 웹 프레임워크에 대한 자동 추적이 가능합니다.
Gin
import (
"github.com/gin-gonic/gin"
"github.com/whatap/go-api/instrumentation/github.com/gin-gonic/gin/whatapgin"
)
func main() {
trace.Init(nil)
defer trace.Shutdown()
r := gin.Default()
r.Use(whatapgin.Middleware())
r.GET("/hello", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "hello"})
})
r.Run(":8080")
}
Echo v4
import (
"github.com/labstack/echo/v4"
"github.com/whatap/go-api/instrumentation/github.com/labstack/echo/v4/whatapecho"
)
func main() {
trace.Init(nil)
defer trace.Shutdown()
e := echo.New()
e.Use(whatapecho.Middleware())
e.GET("/hello", func(c echo.Context) error {
return c.JSON(200, map[string]string{"message": "hello"})
})
e.Start(":8080")
}
Fiber v2
import (
"github.com/gofiber/fiber/v2"
"github.com/whatap/go-api/instrumentation/github.com/gofiber/fiber/v2/whatapfiber"
)
func main() {
trace.Init(nil)
defer trace.Shutdown()
app := fiber.New()
app.Use(whatapfiber.Middleware())
app.Get("/hello", func(c *fiber.Ctx) error {
return c.JSON(fiber.Map{"message": "hello"})
})
app.Listen(":8080")
}
Chi v5
import (
"net/http"
"github.com/go-chi/chi/v5"
"github.com/whatap/go-api/instrumentation/github.com/go-chi/chi/whatapchi"
)
func main() {
trace.Init(nil)
defer trace.Shutdown()
r := chi.NewRouter()
r.Use(whatapchi.Middleware)
r.Get("/hello", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello"))
})
http.ListenAndServe(":8080", r)
}
Gorilla Mux
import (
"net/http"
"github.com/gorilla/mux"
"github.com/whatap/go-api/instrumentation/github.com/gorilla/mux/whatapmux"
)
func main() {
trace.Init(nil)
defer trace.Shutdown()
r := mux.NewRouter()
r.Use(whatapmux.Middleware)
r.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello"))
})
http.ListenAndServe(":8080", r)
}
FastHTTP
import (
"github.com/valyala/fasthttp"
"github.com/whatap/go-api/instrumentation/github.com/valyala/fasthttp/whatapfasthttp"
)
func main() {
trace.Init(nil)
defer trace.Shutdown()
handler := func(ctx *fasthttp.RequestCtx) {
ctx.WriteString("hello")
}
fasthttp.ListenAndServe(":8080", whatapfasthttp.Middleware(handler))
}
net/http 핸들러 래핑
import (
"net/http"
"github.com/whatap/go-api/instrumentation/net/http/whataphttp"
)
func main() {
trace.Init(nil)
defer trace.Shutdown()
// HandleFunc 래핑
http.HandleFunc("/api", whataphttp.Func(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello"))
}))
// Handler 래핑
http.Handle("/handler", whataphttp.Handler(myHandler))
http.ListenAndServe(":8080", nil)
}
database/sql 계측 (whatapsql)
database/sql 패키지를 래핑하여 자동으로 SQL 쿼리를 추적합니다.
import (
_ "github.com/go-sql-driver/mysql"
"github.com/whatap/go-api/instrumentation/database/sql/whatapsql"
"github.com/whatap/go-api/trace"
)
func main() {
trace.Init(nil)
defer trace.Shutdown()
// sql.Open 대신 whatapsql.Open 사용
db, err := whatapsql.Open("mysql", "user:password@tcp(localhost:3306)/dbname")
if err != nil {
panic(err)
}
defer db.Close()
// 이후 db.Query, db.Exec 등이 자동으로 추적됨
rows, err := db.QueryContext(ctx, "SELECT * FROM users WHERE id = ?", 1)
}
OpenContext (트랜잭션 컨텍스트 연결)
// 트랜잭션 컨텍스트와 연결
db, err := whatapsql.OpenContext(ctx, "mysql", dsn)
sqlx 계측
import (
"github.com/whatap/go-api/instrumentation/github.com/jmoiron/sqlx/whatapsqlx"
)
func main() {
trace.Init(nil)
defer trace.Shutdown()
// sqlx.Open 대신 whatapsqlx.Open 사용
db, err := whatapsqlx.Open("mysql", dsn)
// sqlx.Connect 대신 whatapsqlx.Connect 사용
db, err := whatapsqlx.Connect("postgres", dsn)
}
GORM 계측
gorm.io/gorm (GORM v2)
import (
"gorm.io/driver/mysql"
"github.com/whatap/go-api/instrumentation/github.com/go-gorm/gorm/whatapgorm"
)
func main() {
trace.Init(nil)
defer trace.Shutdown()
// gorm.Open 대신 whatapgorm.Open 사용
db, err := whatapgorm.Open(mysql.Open(dsn), &gorm.Config{})
}
github.com/jinzhu/gorm (GORM v1)
import (
"github.com/whatap/go-api/instrumentation/github.com/jinzhu/gorm/whatapgorm"
)
func main() {
trace.Init(nil)
defer trace.Shutdown()
db, err := whatapgorm.Open("mysql", dsn)
}