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 }