Skip to content

Commit

Permalink
Merge pull request #252 from axw/v2-transaction-service-override
Browse files Browse the repository at this point in the history
 elasticapm: add Context.SetFramework
  • Loading branch information
axw authored Oct 3, 2018
2 parents c14a780 + 804f997 commit 324f0c3
Show file tree
Hide file tree
Showing 12 changed files with 180 additions and 33 deletions.
44 changes: 35 additions & 9 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,25 @@ import (

// Context provides methods for setting transaction and error context.
type Context struct {
model model.Context
request model.Request
requestBody model.RequestBody
requestHeaders model.RequestHeaders
requestSocket model.RequestSocket
response model.Response
responseHeaders model.ResponseHeaders
user model.User
captureBodyMask CaptureBodyMode
model model.Context
request model.Request
requestBody model.RequestBody
requestHeaders model.RequestHeaders
requestSocket model.RequestSocket
response model.Response
responseHeaders model.ResponseHeaders
user model.User
service model.Service
serviceFramework model.Framework
captureBodyMask CaptureBodyMode
}

func (c *Context) build() *model.Context {
switch {
case c.model.Request != nil:
case c.model.Response != nil:
case c.model.User != nil:
case c.model.Service != nil:
case len(c.model.Custom) != 0:
case len(c.model.Tags) != 0:
default:
Expand Down Expand Up @@ -75,6 +78,29 @@ func (c *Context) SetTag(key, value string) {
}
}

// SetFramework sets the framework name and version in the context.
//
// This is used for identifying the framework in which the context
// was created, such as Gin or Echo.
//
// If the name is empty, this is a no-op. If version is empty, then
// it will be set to "unspecified".
func (c *Context) SetFramework(name, version string) {
if name == "" {
return
}
if version == "" {
// Framework version is required.
version = "unspecified"
}
c.serviceFramework = model.Framework{
Name: truncateKeyword(name),
Version: truncateKeyword(version),
}
c.service.Framework = &c.serviceFramework
c.model.Service = &c.service
}

// SetHTTPRequest sets details of the HTTP request in the context.
//
// This function relates to server-side requests. Various proxy
Expand Down
49 changes: 38 additions & 11 deletions context_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package elasticapm_test

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/elastic/apm-agent-go"
"github.com/elastic/apm-agent-go/apmtest"
"github.com/elastic/apm-agent-go/model"
"github.com/elastic/apm-agent-go/transport/transporttest"
)

func TestContextUser(t *testing.T) {
Expand All @@ -31,15 +33,40 @@ func TestContextUser(t *testing.T) {
})
}

func testSendTransaction(t *testing.T, f func(tx *elasticapm.Transaction)) model.Transaction {
tracer, r := transporttest.NewRecorderTracer()
defer tracer.Close()

tx := tracer.StartTransaction("name", "type")
f(tx)
tx.End()
tracer.Flush(nil)
func TestContextFramework(t *testing.T) {
t.Run("name_unspecified", func(t *testing.T) {
tx := testSendTransaction(t, func(tx *elasticapm.Transaction) {
tx.Context.SetFramework("", "1.0")
})
assert.Nil(t, tx.Context)
})
t.Run("version_specified", func(t *testing.T) {
tx := testSendTransaction(t, func(tx *elasticapm.Transaction) {
tx.Context.SetFramework("framework", "1.0")
})
require.NotNil(t, tx.Context)
require.NotNil(t, tx.Context.Service)
assert.Equal(t, &model.Framework{
Name: "framework",
Version: "1.0",
}, tx.Context.Service.Framework)
})
t.Run("version_unspecified", func(t *testing.T) {
tx := testSendTransaction(t, func(tx *elasticapm.Transaction) {
tx.Context.SetFramework("framework", "")
})
require.NotNil(t, tx.Context)
require.NotNil(t, tx.Context.Service)
assert.Equal(t, &model.Framework{
Name: "framework",
Version: "unspecified",
}, tx.Context.Service.Framework)
})
}

payloads := r.Payloads()
return payloads.Transactions[0]
func testSendTransaction(t *testing.T, f func(tx *elasticapm.Transaction)) model.Transaction {
transaction, _, _ := apmtest.WithTransaction(func(ctx context.Context) {
f(elasticapm.TransactionFromContext(ctx))
})
return transaction
}
75 changes: 66 additions & 9 deletions model/marshal_fastjson.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 13 additions & 1 deletion model/marshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ func TestMarshalTransaction(t *testing.T) {
"duration": 123.456,
"result": "418",
"context": map[string]interface{}{
"service": map[string]interface{}{
"framework": map[string]interface{}{
"name": "framework-name",
"version": "framework-version",
},
},
"request": map[string]interface{}{
"url": map[string]interface{}{
"full": "https://testing.invalid/foo/bar?baz#qux",
Expand Down Expand Up @@ -539,6 +545,12 @@ func fakeTransaction() model.Transaction {
Tags: map[string]string{
"tag": "urit",
},
Service: &model.Service{
Framework: &model.Framework{
Name: "framework-name",
Version: "framework-version",
},
},
},
SpanCount: model.SpanCount{
Started: 99,
Expand Down Expand Up @@ -597,7 +609,7 @@ func fakeService() *model.Service {
Name: "fake-service",
Version: "1.0.0-rc1",
Environment: "dev",
Agent: model.Agent{
Agent: &model.Agent{
Name: "go",
Version: "0.1.0",
},
Expand Down
7 changes: 5 additions & 2 deletions model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
// Service represents the service handling transactions being traced.
type Service struct {
// Name is the immutable name of the service.
Name string `json:"name"`
Name string `json:"name,omitempty"`

// Version is the version of the service, if it has one.
Version string `json:"version,omitempty"`
Expand All @@ -20,7 +20,7 @@ type Service struct {

// Agent holds information about the Elastic APM agent tracing this
// service's transactions.
Agent Agent `json:"agent"`
Agent *Agent `json:"agent,omitempty"`

// Framework holds information about the service's framework, if any.
Framework *Framework `json:"framework,omitempty"`
Expand Down Expand Up @@ -248,6 +248,9 @@ type Context struct {

// Tags holds user-defined key/value pairs.
Tags map[string]string `json:"tags,omitempty"`

// Service holds values to overrides service-level metadata.
Service *Service `json:"service,omitempty"`
}

// User holds information about an authenticated user.
Expand Down
1 change: 1 addition & 0 deletions module/apmecho/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func (m *middleware) handle(c echo.Context) error {
}

func setContext(ctx *elasticapm.Context, req *http.Request, resp *echo.Response, body *elasticapm.BodyCapturer) {
ctx.SetFramework("echo", echo.Version)
ctx.SetHTTPRequest(req)
ctx.SetHTTPRequestBody(body)
if resp.Committed {
Expand Down
6 changes: 6 additions & 0 deletions module/apmecho/middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ func TestEchoMiddleware(t *testing.T) {
assert.Equal(t, "HTTP 4xx", transaction.Result)

assert.Equal(t, &model.Context{
Service: &model.Service{
Framework: &model.Framework{
Name: "echo",
Version: echo.Version,
},
},
Request: &model.Request{
Socket: &model.RequestSocket{
RemoteAddress: "client.testing",
Expand Down
1 change: 1 addition & 0 deletions module/apmgin/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ func (m *middleware) handle(c *gin.Context) {
}

func setContext(ctx *elasticapm.Context, c *gin.Context, body *elasticapm.BodyCapturer) {
ctx.SetFramework("gin", gin.Version)
ctx.SetHTTPRequest(c.Request)
ctx.SetHTTPRequestBody(body)
if c.Writer.Written() {
Expand Down
6 changes: 6 additions & 0 deletions module/apmgin/middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ func TestMiddleware(t *testing.T) {
assert.Equal(t, "HTTP 2xx", transaction.Result)

assert.Equal(t, &model.Context{
Service: &model.Service{
Framework: &model.Framework{
Name: "gin",
Version: gin.Version,
},
},
Request: &model.Request{
Socket: &model.RequestSocket{
RemoteAddress: "client.testing",
Expand Down
2 changes: 2 additions & 0 deletions module/apmgrpc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func NewUnaryServerInterceptor(o ...ServerOption) grpc.UnaryServerInterceptor {
tx := opts.tracer.StartTransaction(info.FullMethod, "grpc")
ctx = elasticapm.ContextWithTransaction(ctx, tx)
defer tx.End()
tx.Context.SetFramework("grpc", grpc.Version)

if tx.Sampled() {
p, ok := peer.FromContext(ctx)
Expand All @@ -61,6 +62,7 @@ func NewUnaryServerInterceptor(o ...ServerOption) grpc.UnaryServerInterceptor {
if r != nil {
e := opts.tracer.Recovered(r)
e.SetTransaction(tx)
e.Context.SetFramework("grpc", grpc.Version)
e.Handled = opts.recover
e.Send()
if opts.recover {
Expand Down
6 changes: 6 additions & 0 deletions module/apmgrpc/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ func testServerTransactionHappy(t *testing.T, p testParams) {
assert.Contains(t, grpcContext, "peer.address")
delete(grpcContext, "peer.address")
assert.Equal(t, &model.Context{
Service: &model.Service{
Framework: &model.Framework{
Name: "grpc",
Version: grpc.Version,
},
},
Custom: model.IfaceMap{{
Key: "grpc",
Value: map[string]interface{}{},
Expand Down
2 changes: 1 addition & 1 deletion utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func makeService(name, version, environment string) model.Service {
Name: truncateKeyword(name),
Version: truncateKeyword(version),
Environment: truncateKeyword(environment),
Agent: goAgent,
Agent: &goAgent,
Language: &goLanguage,
Runtime: &goRuntime,
}
Expand Down

0 comments on commit 324f0c3

Please sign in to comment.