github.com/waldiirawan/apm-agent-go/v2@v2.2.2/spancontext.go (about)

     1  // Licensed to Elasticsearch B.V. under one or more contributor
     2  // license agreements. See the NOTICE file distributed with
     3  // this work for additional information regarding copyright
     4  // ownership. Elasticsearch B.V. licenses this file to you under
     5  // the Apache License, Version 2.0 (the "License"); you may
     6  // not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //     http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing,
    12  // software distributed under the License is distributed on an
    13  // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    14  // KIND, either express or implied.  See the License for the
    15  // specific language governing permissions and limitations
    16  // under the License.
    17  
    18  package apm // import "github.com/waldiirawan/apm-agent-go/v2"
    19  
    20  import (
    21  	"fmt"
    22  	"net/http"
    23  	"net/url"
    24  	"strings"
    25  
    26  	"github.com/waldiirawan/apm-agent-go/v2/internal/apmhttputil"
    27  	"github.com/waldiirawan/apm-agent-go/v2/model"
    28  )
    29  
    30  // SpanContext provides methods for setting span context.
    31  type SpanContext struct {
    32  	model                model.SpanContext
    33  	destination          model.DestinationSpanContext
    34  	destinationService   model.DestinationServiceSpanContext
    35  	service              model.ServiceSpanContext
    36  	serviceTarget        model.ServiceTargetSpanContext
    37  	destinationCloud     model.DestinationCloudSpanContext
    38  	message              model.MessageSpanContext
    39  	databaseRowsAffected int64
    40  	database             model.DatabaseSpanContext
    41  	http                 model.HTTPSpanContext
    42  	otel                 *model.OTel
    43  
    44  	// If SetDestinationService has been called, we do not auto-set its
    45  	// resource value on span end.
    46  	setDestinationServiceCalled bool
    47  
    48  	// If SetServiceTarget has been called, we do not auto-set its
    49  	// values on span end.
    50  	setServiceTargetCalled bool
    51  }
    52  
    53  // DatabaseSpanContext holds database span context.
    54  type DatabaseSpanContext struct {
    55  	// Instance holds the database instance name.
    56  	Instance string
    57  
    58  	// Statement holds the statement executed in the span,
    59  	// e.g. "SELECT * FROM foo".
    60  	Statement string
    61  
    62  	// Type holds the database type, e.g. "sql".
    63  	Type string
    64  
    65  	// User holds the username used for database access.
    66  	User string
    67  }
    68  
    69  // ServiceSpanContext holds contextual information about the service
    70  // for a span that relates to an operation involving an external service.
    71  type ServiceSpanContext struct {
    72  	// Target holds the destination service.
    73  	Target *ServiceTargetSpanContext
    74  }
    75  
    76  // ServiceTargetSpanContext fields replace the `span.destination.service.*`
    77  // fields that are deprecated.
    78  type ServiceTargetSpanContext struct {
    79  	// Type holds the destination service type.
    80  	Type string
    81  
    82  	// Name holds the destination service name.
    83  	Name string
    84  }
    85  
    86  // DestinationServiceSpanContext holds destination service span span.
    87  type DestinationServiceSpanContext struct {
    88  	// Name holds a name for the destination service, which may be used
    89  	// for grouping and labeling in service maps. Deprecated.
    90  	//
    91  	// Deprecated: replaced by `service.target.{type,name}`.
    92  	Name string
    93  
    94  	// Resource holds an identifier for a destination service resource,
    95  	// such as a message queue.
    96  	//
    97  	// Deprecated: replaced by `service.target.{type,name}`.
    98  	Resource string
    99  }
   100  
   101  // DestinationCloudSpanContext holds contextual information about a
   102  // destination cloud.
   103  type DestinationCloudSpanContext struct {
   104  	// Region holds the destination cloud region.
   105  	Region string
   106  }
   107  
   108  // MessageSpanContext holds contextual information about a message.
   109  type MessageSpanContext struct {
   110  	// QueueName holds the message queue name.
   111  	QueueName string
   112  }
   113  
   114  func (c *SpanContext) build() *model.SpanContext {
   115  	switch {
   116  	case len(c.model.Tags) != 0:
   117  	case c.model.Message != nil:
   118  	case c.model.Database != nil:
   119  	case c.model.HTTP != nil:
   120  	case c.model.Destination != nil:
   121  	default:
   122  		return nil
   123  	}
   124  	return &c.model
   125  }
   126  
   127  func (c *SpanContext) reset() {
   128  	*c = SpanContext{
   129  		model: model.SpanContext{
   130  			Tags: c.model.Tags[:0],
   131  		},
   132  	}
   133  }
   134  
   135  // SetOTelAttributes sets the provided OpenTelemetry attributes.
   136  func (c *SpanContext) SetOTelAttributes(m map[string]interface{}) {
   137  	if c.otel == nil {
   138  		c.otel = &model.OTel{}
   139  	}
   140  	c.otel.Attributes = m
   141  }
   142  
   143  // SetOTelSpanKind sets the provided SpanKind.
   144  func (c *SpanContext) SetOTelSpanKind(spanKind string) {
   145  	if c.otel == nil {
   146  		c.otel = &model.OTel{}
   147  	}
   148  	c.otel.SpanKind = spanKind
   149  }
   150  
   151  // SetLabel sets a label in the context.
   152  //
   153  // Invalid characters ('.', '*', and '"') in the key will be replaced with
   154  // underscores.
   155  //
   156  // If the value is numerical or boolean, then it will be sent to the server
   157  // as a JSON number or boolean; otherwise it will converted to a string, using
   158  // `fmt.Sprint` if necessary. String values longer than 1024 characters will
   159  // be truncated.
   160  func (c *SpanContext) SetLabel(key string, value interface{}) {
   161  	// Note that we do not attempt to de-duplicate the keys.
   162  	// This is OK, since json.Unmarshal will always take the
   163  	// final instance.
   164  	c.model.Tags = append(c.model.Tags, model.IfaceMapItem{
   165  		Key:   cleanLabelKey(key),
   166  		Value: makeLabelValue(value),
   167  	})
   168  }
   169  
   170  // SetDatabase sets the span context for database-related operations.
   171  func (c *SpanContext) SetDatabase(db DatabaseSpanContext) {
   172  	c.database = model.DatabaseSpanContext{
   173  		Instance:  truncateString(db.Instance),
   174  		Statement: truncateLongString(db.Statement),
   175  		Type:      truncateString(db.Type),
   176  		User:      truncateString(db.User),
   177  	}
   178  	c.model.Database = &c.database
   179  }
   180  
   181  // SetDatabaseRowsAffected records the number of rows affected by
   182  // a database operation.
   183  func (c *SpanContext) SetDatabaseRowsAffected(n int64) {
   184  	c.databaseRowsAffected = n
   185  	c.database.RowsAffected = &c.databaseRowsAffected
   186  }
   187  
   188  // SetHTTPRequest sets the details of the HTTP request in the context.
   189  //
   190  // This function relates to client requests. If the request URL contains
   191  // user info, it will be removed and excluded from the stored URL.
   192  //
   193  // SetHTTPRequest makes implicit calls to SetDestinationAddress and
   194  // SetDestinationService, using details from req.URL.
   195  func (c *SpanContext) SetHTTPRequest(req *http.Request) {
   196  	if req.URL == nil {
   197  		return
   198  	}
   199  	c.http.URL = req.URL
   200  	c.model.HTTP = &c.http
   201  
   202  	addr, port := apmhttputil.DestinationAddr(req)
   203  	c.SetDestinationAddress(addr, port)
   204  
   205  	destinationServiceURL := url.URL{Scheme: req.URL.Scheme, Host: req.URL.Host}
   206  	destinationServiceResource := destinationServiceURL.Host
   207  	if port != 0 && port == apmhttputil.SchemeDefaultPort(req.URL.Scheme) {
   208  		var hasDefaultPort bool
   209  		if n := len(destinationServiceURL.Host); n > 0 && destinationServiceURL.Host[n-1] != ']' {
   210  			if i := strings.LastIndexByte(destinationServiceURL.Host, ':'); i != -1 {
   211  				// Remove the default port from destination.service.name.
   212  				destinationServiceURL.Host = destinationServiceURL.Host[:i]
   213  				hasDefaultPort = true
   214  			}
   215  		}
   216  		if !hasDefaultPort {
   217  			// Add the default port to destination.service.resource.
   218  			destinationServiceResource = fmt.Sprintf("%s:%d", destinationServiceResource, port)
   219  		}
   220  	}
   221  	c.SetDestinationService(DestinationServiceSpanContext{
   222  		Name:     destinationServiceURL.String(),
   223  		Resource: destinationServiceResource,
   224  	})
   225  	c.SetServiceTarget(ServiceTargetSpanContext{
   226  		Name: destinationServiceResource,
   227  	})
   228  }
   229  
   230  // SetHTTPStatusCode records the HTTP response status code.
   231  //
   232  // If, when the transaction ends, its Outcome field has not
   233  // been explicitly set, it will be set based on the status code:
   234  // "success" if statusCode < 400, and "failure" otherwise.
   235  func (c *SpanContext) SetHTTPStatusCode(statusCode int) {
   236  	c.http.StatusCode = statusCode
   237  	c.model.HTTP = &c.http
   238  }
   239  
   240  // SetDestinationAddress sets the destination address and port in the context.
   241  //
   242  // SetDestinationAddress has no effect when called with an empty addr.
   243  func (c *SpanContext) SetDestinationAddress(addr string, port int) {
   244  	if addr != "" {
   245  		c.destination.Address = truncateString(addr)
   246  		c.destination.Port = port
   247  		c.model.Destination = &c.destination
   248  	}
   249  }
   250  
   251  // SetMessage sets the message info in the context.
   252  //
   253  // message.Name is required. If it is empty, then SetMessage is a no-op.
   254  func (c *SpanContext) SetMessage(message MessageSpanContext) {
   255  	if message.QueueName == "" {
   256  		return
   257  	}
   258  	c.message.Queue = &model.MessageQueueSpanContext{
   259  		Name: truncateString(message.QueueName),
   260  	}
   261  	c.model.Message = &c.message
   262  }
   263  
   264  // SetDestinationService sets the destination service info in the context.
   265  //
   266  // Both service.Name and service.Resource are required. If either is empty,
   267  // then SetDestinationService is a no-op.
   268  //
   269  // Deprecated: use SetServiceTarget
   270  func (c *SpanContext) SetDestinationService(service DestinationServiceSpanContext) {
   271  	c.setDestinationServiceCalled = true
   272  	if service.Resource == "" {
   273  		return
   274  	}
   275  	c.destinationService.Name = truncateString(service.Name)
   276  	c.destinationService.Resource = truncateString(service.Resource)
   277  	c.destination.Service = &c.destinationService
   278  	c.model.Destination = &c.destination
   279  }
   280  
   281  // SetServiceTarget sets the service target info in the context.
   282  func (c *SpanContext) SetServiceTarget(service ServiceTargetSpanContext) {
   283  	c.setServiceTargetCalled = true
   284  	c.serviceTarget.Type = truncateString(service.Type)
   285  	c.serviceTarget.Name = truncateString(service.Name)
   286  	c.service.Target = &c.serviceTarget
   287  	c.model.Service = &c.service
   288  }
   289  
   290  // SetDestinationCloud sets the destination cloud info in the context.
   291  func (c *SpanContext) SetDestinationCloud(cloud DestinationCloudSpanContext) {
   292  	c.destinationCloud.Region = truncateString(cloud.Region)
   293  	c.destination.Cloud = &c.destinationCloud
   294  	c.model.Destination = &c.destination
   295  }
   296  
   297  // outcome returns the outcome to assign to the associated span, based on
   298  // context (e.g. HTTP status code).
   299  func (c *SpanContext) outcome() string {
   300  	if c.http.StatusCode != 0 {
   301  		if c.http.StatusCode < 400 {
   302  			return "success"
   303  		}
   304  		return "failure"
   305  	}
   306  	return ""
   307  }