| Abhay Kumar | 40252eb | 2025-10-13 13:25:53 +0000 | [diff] [blame] | 1 | // Copyright The OpenTelemetry Authors |
| 2 | // SPDX-License-Identifier: Apache-2.0 |
| 3 | |
| 4 | package trace // import "go.opentelemetry.io/otel/trace" |
| 5 | |
| 6 | import ( |
| Abhay Kumar | 40252eb | 2025-10-13 13:25:53 +0000 | [diff] [blame] | 7 | "encoding/json" |
| 8 | ) |
| 9 | |
| 10 | const ( |
| 11 | // FlagsSampled is a bitmask with the sampled bit set. A SpanContext |
| 12 | // with the sampling bit set means the span is sampled. |
| 13 | FlagsSampled = TraceFlags(0x01) |
| 14 | |
| 15 | errInvalidHexID errorConst = "trace-id and span-id can only contain [0-9a-f] characters, all lowercase" |
| 16 | |
| 17 | errInvalidTraceIDLength errorConst = "hex encoded trace-id must have length equals to 32" |
| 18 | errNilTraceID errorConst = "trace-id can't be all zero" |
| 19 | |
| 20 | errInvalidSpanIDLength errorConst = "hex encoded span-id must have length equals to 16" |
| 21 | errNilSpanID errorConst = "span-id can't be all zero" |
| 22 | ) |
| 23 | |
| 24 | type errorConst string |
| 25 | |
| 26 | func (e errorConst) Error() string { |
| 27 | return string(e) |
| 28 | } |
| 29 | |
| 30 | // TraceID is a unique identity of a trace. |
| 31 | // nolint:revive // revive complains about stutter of `trace.TraceID`. |
| 32 | type TraceID [16]byte |
| 33 | |
| 34 | var ( |
| 35 | nilTraceID TraceID |
| 36 | _ json.Marshaler = nilTraceID |
| 37 | ) |
| 38 | |
| Abhay Kumar | 062cda5 | 2025-12-23 06:49:37 +0000 | [diff] [blame] | 39 | // IsValid reports whether the trace TraceID is valid. A valid trace ID does |
| Abhay Kumar | 40252eb | 2025-10-13 13:25:53 +0000 | [diff] [blame] | 40 | // not consist of zeros only. |
| 41 | func (t TraceID) IsValid() bool { |
| Abhay Kumar | 062cda5 | 2025-12-23 06:49:37 +0000 | [diff] [blame] | 42 | return t != nilTraceID |
| Abhay Kumar | 40252eb | 2025-10-13 13:25:53 +0000 | [diff] [blame] | 43 | } |
| 44 | |
| 45 | // MarshalJSON implements a custom marshal function to encode TraceID |
| 46 | // as a hex string. |
| 47 | func (t TraceID) MarshalJSON() ([]byte, error) { |
| Abhay Kumar | 062cda5 | 2025-12-23 06:49:37 +0000 | [diff] [blame] | 48 | b := [32 + 2]byte{0: '"', 33: '"'} |
| 49 | h := t.hexBytes() |
| 50 | copy(b[1:], h[:]) |
| 51 | return b[:], nil |
| Abhay Kumar | 40252eb | 2025-10-13 13:25:53 +0000 | [diff] [blame] | 52 | } |
| 53 | |
| 54 | // String returns the hex string representation form of a TraceID. |
| 55 | func (t TraceID) String() string { |
| Abhay Kumar | 062cda5 | 2025-12-23 06:49:37 +0000 | [diff] [blame] | 56 | h := t.hexBytes() |
| 57 | return string(h[:]) |
| 58 | } |
| 59 | |
| 60 | // hexBytes returns the hex string representation form of a TraceID. |
| 61 | func (t TraceID) hexBytes() [32]byte { |
| 62 | return [32]byte{ |
| 63 | hexLU[t[0x0]>>4], hexLU[t[0x0]&0xf], |
| 64 | hexLU[t[0x1]>>4], hexLU[t[0x1]&0xf], |
| 65 | hexLU[t[0x2]>>4], hexLU[t[0x2]&0xf], |
| 66 | hexLU[t[0x3]>>4], hexLU[t[0x3]&0xf], |
| 67 | hexLU[t[0x4]>>4], hexLU[t[0x4]&0xf], |
| 68 | hexLU[t[0x5]>>4], hexLU[t[0x5]&0xf], |
| 69 | hexLU[t[0x6]>>4], hexLU[t[0x6]&0xf], |
| 70 | hexLU[t[0x7]>>4], hexLU[t[0x7]&0xf], |
| 71 | hexLU[t[0x8]>>4], hexLU[t[0x8]&0xf], |
| 72 | hexLU[t[0x9]>>4], hexLU[t[0x9]&0xf], |
| 73 | hexLU[t[0xa]>>4], hexLU[t[0xa]&0xf], |
| 74 | hexLU[t[0xb]>>4], hexLU[t[0xb]&0xf], |
| 75 | hexLU[t[0xc]>>4], hexLU[t[0xc]&0xf], |
| 76 | hexLU[t[0xd]>>4], hexLU[t[0xd]&0xf], |
| 77 | hexLU[t[0xe]>>4], hexLU[t[0xe]&0xf], |
| 78 | hexLU[t[0xf]>>4], hexLU[t[0xf]&0xf], |
| 79 | } |
| Abhay Kumar | 40252eb | 2025-10-13 13:25:53 +0000 | [diff] [blame] | 80 | } |
| 81 | |
| 82 | // SpanID is a unique identity of a span in a trace. |
| 83 | type SpanID [8]byte |
| 84 | |
| 85 | var ( |
| 86 | nilSpanID SpanID |
| 87 | _ json.Marshaler = nilSpanID |
| 88 | ) |
| 89 | |
| Abhay Kumar | 062cda5 | 2025-12-23 06:49:37 +0000 | [diff] [blame] | 90 | // IsValid reports whether the SpanID is valid. A valid SpanID does not consist |
| Abhay Kumar | 40252eb | 2025-10-13 13:25:53 +0000 | [diff] [blame] | 91 | // of zeros only. |
| 92 | func (s SpanID) IsValid() bool { |
| Abhay Kumar | 062cda5 | 2025-12-23 06:49:37 +0000 | [diff] [blame] | 93 | return s != nilSpanID |
| Abhay Kumar | 40252eb | 2025-10-13 13:25:53 +0000 | [diff] [blame] | 94 | } |
| 95 | |
| 96 | // MarshalJSON implements a custom marshal function to encode SpanID |
| 97 | // as a hex string. |
| 98 | func (s SpanID) MarshalJSON() ([]byte, error) { |
| Abhay Kumar | 062cda5 | 2025-12-23 06:49:37 +0000 | [diff] [blame] | 99 | b := [16 + 2]byte{0: '"', 17: '"'} |
| 100 | h := s.hexBytes() |
| 101 | copy(b[1:], h[:]) |
| 102 | return b[:], nil |
| Abhay Kumar | 40252eb | 2025-10-13 13:25:53 +0000 | [diff] [blame] | 103 | } |
| 104 | |
| 105 | // String returns the hex string representation form of a SpanID. |
| 106 | func (s SpanID) String() string { |
| Abhay Kumar | 062cda5 | 2025-12-23 06:49:37 +0000 | [diff] [blame] | 107 | b := s.hexBytes() |
| 108 | return string(b[:]) |
| 109 | } |
| 110 | |
| 111 | func (s SpanID) hexBytes() [16]byte { |
| 112 | return [16]byte{ |
| 113 | hexLU[s[0]>>4], hexLU[s[0]&0xf], |
| 114 | hexLU[s[1]>>4], hexLU[s[1]&0xf], |
| 115 | hexLU[s[2]>>4], hexLU[s[2]&0xf], |
| 116 | hexLU[s[3]>>4], hexLU[s[3]&0xf], |
| 117 | hexLU[s[4]>>4], hexLU[s[4]&0xf], |
| 118 | hexLU[s[5]>>4], hexLU[s[5]&0xf], |
| 119 | hexLU[s[6]>>4], hexLU[s[6]&0xf], |
| 120 | hexLU[s[7]>>4], hexLU[s[7]&0xf], |
| 121 | } |
| Abhay Kumar | 40252eb | 2025-10-13 13:25:53 +0000 | [diff] [blame] | 122 | } |
| 123 | |
| 124 | // TraceIDFromHex returns a TraceID from a hex string if it is compliant with |
| 125 | // the W3C trace-context specification. See more at |
| 126 | // https://www.w3.org/TR/trace-context/#trace-id |
| 127 | // nolint:revive // revive complains about stutter of `trace.TraceIDFromHex`. |
| 128 | func TraceIDFromHex(h string) (TraceID, error) { |
| Abhay Kumar | 40252eb | 2025-10-13 13:25:53 +0000 | [diff] [blame] | 129 | if len(h) != 32 { |
| Abhay Kumar | 062cda5 | 2025-12-23 06:49:37 +0000 | [diff] [blame] | 130 | return [16]byte{}, errInvalidTraceIDLength |
| Abhay Kumar | 40252eb | 2025-10-13 13:25:53 +0000 | [diff] [blame] | 131 | } |
| Abhay Kumar | 062cda5 | 2025-12-23 06:49:37 +0000 | [diff] [blame] | 132 | var b [16]byte |
| 133 | invalidMark := byte(0) |
| 134 | for i := 0; i < len(h); i += 4 { |
| 135 | b[i/2] = (hexRev[h[i]] << 4) | hexRev[h[i+1]] |
| 136 | b[i/2+1] = (hexRev[h[i+2]] << 4) | hexRev[h[i+3]] |
| 137 | invalidMark |= hexRev[h[i]] | hexRev[h[i+1]] | hexRev[h[i+2]] | hexRev[h[i+3]] |
| Abhay Kumar | 40252eb | 2025-10-13 13:25:53 +0000 | [diff] [blame] | 138 | } |
| Abhay Kumar | 062cda5 | 2025-12-23 06:49:37 +0000 | [diff] [blame] | 139 | // If the upper 4 bits of any byte are not zero, there was an invalid hex |
| 140 | // character since invalid hex characters are 0xff in hexRev. |
| 141 | if invalidMark&0xf0 != 0 { |
| 142 | return [16]byte{}, errInvalidHexID |
| Abhay Kumar | 40252eb | 2025-10-13 13:25:53 +0000 | [diff] [blame] | 143 | } |
| Abhay Kumar | 062cda5 | 2025-12-23 06:49:37 +0000 | [diff] [blame] | 144 | // If we didn't set any bits, then h was all zeros. |
| 145 | if invalidMark == 0 { |
| 146 | return [16]byte{}, errNilTraceID |
| 147 | } |
| 148 | return b, nil |
| Abhay Kumar | 40252eb | 2025-10-13 13:25:53 +0000 | [diff] [blame] | 149 | } |
| 150 | |
| 151 | // SpanIDFromHex returns a SpanID from a hex string if it is compliant |
| 152 | // with the w3c trace-context specification. |
| 153 | // See more at https://www.w3.org/TR/trace-context/#parent-id |
| 154 | func SpanIDFromHex(h string) (SpanID, error) { |
| Abhay Kumar | 40252eb | 2025-10-13 13:25:53 +0000 | [diff] [blame] | 155 | if len(h) != 16 { |
| Abhay Kumar | 062cda5 | 2025-12-23 06:49:37 +0000 | [diff] [blame] | 156 | return [8]byte{}, errInvalidSpanIDLength |
| Abhay Kumar | 40252eb | 2025-10-13 13:25:53 +0000 | [diff] [blame] | 157 | } |
| Abhay Kumar | 062cda5 | 2025-12-23 06:49:37 +0000 | [diff] [blame] | 158 | var b [8]byte |
| 159 | invalidMark := byte(0) |
| 160 | for i := 0; i < len(h); i += 4 { |
| 161 | b[i/2] = (hexRev[h[i]] << 4) | hexRev[h[i+1]] |
| 162 | b[i/2+1] = (hexRev[h[i+2]] << 4) | hexRev[h[i+3]] |
| 163 | invalidMark |= hexRev[h[i]] | hexRev[h[i+1]] | hexRev[h[i+2]] | hexRev[h[i+3]] |
| Abhay Kumar | 40252eb | 2025-10-13 13:25:53 +0000 | [diff] [blame] | 164 | } |
| Abhay Kumar | 062cda5 | 2025-12-23 06:49:37 +0000 | [diff] [blame] | 165 | // If the upper 4 bits of any byte are not zero, there was an invalid hex |
| 166 | // character since invalid hex characters are 0xff in hexRev. |
| 167 | if invalidMark&0xf0 != 0 { |
| 168 | return [8]byte{}, errInvalidHexID |
| Abhay Kumar | 40252eb | 2025-10-13 13:25:53 +0000 | [diff] [blame] | 169 | } |
| Abhay Kumar | 062cda5 | 2025-12-23 06:49:37 +0000 | [diff] [blame] | 170 | // If we didn't set any bits, then h was all zeros. |
| 171 | if invalidMark == 0 { |
| 172 | return [8]byte{}, errNilSpanID |
| Abhay Kumar | 40252eb | 2025-10-13 13:25:53 +0000 | [diff] [blame] | 173 | } |
| Abhay Kumar | 062cda5 | 2025-12-23 06:49:37 +0000 | [diff] [blame] | 174 | return b, nil |
| Abhay Kumar | 40252eb | 2025-10-13 13:25:53 +0000 | [diff] [blame] | 175 | } |
| 176 | |
| 177 | // TraceFlags contains flags that can be set on a SpanContext. |
| 178 | type TraceFlags byte //nolint:revive // revive complains about stutter of `trace.TraceFlags`. |
| 179 | |
| Abhay Kumar | 062cda5 | 2025-12-23 06:49:37 +0000 | [diff] [blame] | 180 | // IsSampled reports whether the sampling bit is set in the TraceFlags. |
| Abhay Kumar | 40252eb | 2025-10-13 13:25:53 +0000 | [diff] [blame] | 181 | func (tf TraceFlags) IsSampled() bool { |
| 182 | return tf&FlagsSampled == FlagsSampled |
| 183 | } |
| 184 | |
| 185 | // WithSampled sets the sampling bit in a new copy of the TraceFlags. |
| 186 | func (tf TraceFlags) WithSampled(sampled bool) TraceFlags { // nolint:revive // sampled is not a control flag. |
| 187 | if sampled { |
| 188 | return tf | FlagsSampled |
| 189 | } |
| 190 | |
| 191 | return tf &^ FlagsSampled |
| 192 | } |
| 193 | |
| 194 | // MarshalJSON implements a custom marshal function to encode TraceFlags |
| 195 | // as a hex string. |
| 196 | func (tf TraceFlags) MarshalJSON() ([]byte, error) { |
| Abhay Kumar | 062cda5 | 2025-12-23 06:49:37 +0000 | [diff] [blame] | 197 | b := [2 + 2]byte{0: '"', 3: '"'} |
| 198 | h := tf.hexBytes() |
| 199 | copy(b[1:], h[:]) |
| 200 | return b[:], nil |
| Abhay Kumar | 40252eb | 2025-10-13 13:25:53 +0000 | [diff] [blame] | 201 | } |
| 202 | |
| 203 | // String returns the hex string representation form of TraceFlags. |
| 204 | func (tf TraceFlags) String() string { |
| Abhay Kumar | 062cda5 | 2025-12-23 06:49:37 +0000 | [diff] [blame] | 205 | h := tf.hexBytes() |
| 206 | return string(h[:]) |
| 207 | } |
| 208 | |
| 209 | func (tf TraceFlags) hexBytes() [2]byte { |
| 210 | return [2]byte{hexLU[tf>>4], hexLU[tf&0xf]} |
| Abhay Kumar | 40252eb | 2025-10-13 13:25:53 +0000 | [diff] [blame] | 211 | } |
| 212 | |
| 213 | // SpanContextConfig contains mutable fields usable for constructing |
| 214 | // an immutable SpanContext. |
| 215 | type SpanContextConfig struct { |
| 216 | TraceID TraceID |
| 217 | SpanID SpanID |
| 218 | TraceFlags TraceFlags |
| 219 | TraceState TraceState |
| 220 | Remote bool |
| 221 | } |
| 222 | |
| 223 | // NewSpanContext constructs a SpanContext using values from the provided |
| 224 | // SpanContextConfig. |
| 225 | func NewSpanContext(config SpanContextConfig) SpanContext { |
| 226 | return SpanContext{ |
| 227 | traceID: config.TraceID, |
| 228 | spanID: config.SpanID, |
| 229 | traceFlags: config.TraceFlags, |
| 230 | traceState: config.TraceState, |
| 231 | remote: config.Remote, |
| 232 | } |
| 233 | } |
| 234 | |
| 235 | // SpanContext contains identifying trace information about a Span. |
| 236 | type SpanContext struct { |
| 237 | traceID TraceID |
| 238 | spanID SpanID |
| 239 | traceFlags TraceFlags |
| 240 | traceState TraceState |
| 241 | remote bool |
| 242 | } |
| 243 | |
| 244 | var _ json.Marshaler = SpanContext{} |
| 245 | |
| Abhay Kumar | 062cda5 | 2025-12-23 06:49:37 +0000 | [diff] [blame] | 246 | // IsValid reports whether the SpanContext is valid. A valid span context has a |
| Abhay Kumar | 40252eb | 2025-10-13 13:25:53 +0000 | [diff] [blame] | 247 | // valid TraceID and SpanID. |
| 248 | func (sc SpanContext) IsValid() bool { |
| 249 | return sc.HasTraceID() && sc.HasSpanID() |
| 250 | } |
| 251 | |
| Abhay Kumar | 062cda5 | 2025-12-23 06:49:37 +0000 | [diff] [blame] | 252 | // IsRemote reports whether the SpanContext represents a remotely-created Span. |
| Abhay Kumar | 40252eb | 2025-10-13 13:25:53 +0000 | [diff] [blame] | 253 | func (sc SpanContext) IsRemote() bool { |
| 254 | return sc.remote |
| 255 | } |
| 256 | |
| 257 | // WithRemote returns a copy of sc with the Remote property set to remote. |
| 258 | func (sc SpanContext) WithRemote(remote bool) SpanContext { |
| 259 | return SpanContext{ |
| 260 | traceID: sc.traceID, |
| 261 | spanID: sc.spanID, |
| 262 | traceFlags: sc.traceFlags, |
| 263 | traceState: sc.traceState, |
| 264 | remote: remote, |
| 265 | } |
| 266 | } |
| 267 | |
| 268 | // TraceID returns the TraceID from the SpanContext. |
| 269 | func (sc SpanContext) TraceID() TraceID { |
| 270 | return sc.traceID |
| 271 | } |
| 272 | |
| Abhay Kumar | 062cda5 | 2025-12-23 06:49:37 +0000 | [diff] [blame] | 273 | // HasTraceID reports whether the SpanContext has a valid TraceID. |
| Abhay Kumar | 40252eb | 2025-10-13 13:25:53 +0000 | [diff] [blame] | 274 | func (sc SpanContext) HasTraceID() bool { |
| 275 | return sc.traceID.IsValid() |
| 276 | } |
| 277 | |
| 278 | // WithTraceID returns a new SpanContext with the TraceID replaced. |
| 279 | func (sc SpanContext) WithTraceID(traceID TraceID) SpanContext { |
| 280 | return SpanContext{ |
| 281 | traceID: traceID, |
| 282 | spanID: sc.spanID, |
| 283 | traceFlags: sc.traceFlags, |
| 284 | traceState: sc.traceState, |
| 285 | remote: sc.remote, |
| 286 | } |
| 287 | } |
| 288 | |
| 289 | // SpanID returns the SpanID from the SpanContext. |
| 290 | func (sc SpanContext) SpanID() SpanID { |
| 291 | return sc.spanID |
| 292 | } |
| 293 | |
| Abhay Kumar | 062cda5 | 2025-12-23 06:49:37 +0000 | [diff] [blame] | 294 | // HasSpanID reports whether the SpanContext has a valid SpanID. |
| Abhay Kumar | 40252eb | 2025-10-13 13:25:53 +0000 | [diff] [blame] | 295 | func (sc SpanContext) HasSpanID() bool { |
| 296 | return sc.spanID.IsValid() |
| 297 | } |
| 298 | |
| 299 | // WithSpanID returns a new SpanContext with the SpanID replaced. |
| 300 | func (sc SpanContext) WithSpanID(spanID SpanID) SpanContext { |
| 301 | return SpanContext{ |
| 302 | traceID: sc.traceID, |
| 303 | spanID: spanID, |
| 304 | traceFlags: sc.traceFlags, |
| 305 | traceState: sc.traceState, |
| 306 | remote: sc.remote, |
| 307 | } |
| 308 | } |
| 309 | |
| 310 | // TraceFlags returns the flags from the SpanContext. |
| 311 | func (sc SpanContext) TraceFlags() TraceFlags { |
| 312 | return sc.traceFlags |
| 313 | } |
| 314 | |
| Abhay Kumar | 062cda5 | 2025-12-23 06:49:37 +0000 | [diff] [blame] | 315 | // IsSampled reports whether the sampling bit is set in the SpanContext's TraceFlags. |
| Abhay Kumar | 40252eb | 2025-10-13 13:25:53 +0000 | [diff] [blame] | 316 | func (sc SpanContext) IsSampled() bool { |
| 317 | return sc.traceFlags.IsSampled() |
| 318 | } |
| 319 | |
| 320 | // WithTraceFlags returns a new SpanContext with the TraceFlags replaced. |
| 321 | func (sc SpanContext) WithTraceFlags(flags TraceFlags) SpanContext { |
| 322 | return SpanContext{ |
| 323 | traceID: sc.traceID, |
| 324 | spanID: sc.spanID, |
| 325 | traceFlags: flags, |
| 326 | traceState: sc.traceState, |
| 327 | remote: sc.remote, |
| 328 | } |
| 329 | } |
| 330 | |
| 331 | // TraceState returns the TraceState from the SpanContext. |
| 332 | func (sc SpanContext) TraceState() TraceState { |
| 333 | return sc.traceState |
| 334 | } |
| 335 | |
| 336 | // WithTraceState returns a new SpanContext with the TraceState replaced. |
| 337 | func (sc SpanContext) WithTraceState(state TraceState) SpanContext { |
| 338 | return SpanContext{ |
| 339 | traceID: sc.traceID, |
| 340 | spanID: sc.spanID, |
| 341 | traceFlags: sc.traceFlags, |
| 342 | traceState: state, |
| 343 | remote: sc.remote, |
| 344 | } |
| 345 | } |
| 346 | |
| Abhay Kumar | 062cda5 | 2025-12-23 06:49:37 +0000 | [diff] [blame] | 347 | // Equal reports whether two SpanContext values are equal. |
| Abhay Kumar | 40252eb | 2025-10-13 13:25:53 +0000 | [diff] [blame] | 348 | func (sc SpanContext) Equal(other SpanContext) bool { |
| 349 | return sc.traceID == other.traceID && |
| 350 | sc.spanID == other.spanID && |
| 351 | sc.traceFlags == other.traceFlags && |
| 352 | sc.traceState.String() == other.traceState.String() && |
| 353 | sc.remote == other.remote |
| 354 | } |
| 355 | |
| 356 | // MarshalJSON implements a custom marshal function to encode a SpanContext. |
| 357 | func (sc SpanContext) MarshalJSON() ([]byte, error) { |
| 358 | return json.Marshal(SpanContextConfig{ |
| 359 | TraceID: sc.traceID, |
| 360 | SpanID: sc.spanID, |
| 361 | TraceFlags: sc.traceFlags, |
| 362 | TraceState: sc.traceState, |
| 363 | Remote: sc.remote, |
| 364 | }) |
| 365 | } |