| 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 ( |
| 7 | "bytes" |
| 8 | "encoding/hex" |
| 9 | "encoding/json" |
| 10 | ) |
| 11 | |
| 12 | const ( |
| 13 | // FlagsSampled is a bitmask with the sampled bit set. A SpanContext |
| 14 | // with the sampling bit set means the span is sampled. |
| 15 | FlagsSampled = TraceFlags(0x01) |
| 16 | |
| 17 | errInvalidHexID errorConst = "trace-id and span-id can only contain [0-9a-f] characters, all lowercase" |
| 18 | |
| 19 | errInvalidTraceIDLength errorConst = "hex encoded trace-id must have length equals to 32" |
| 20 | errNilTraceID errorConst = "trace-id can't be all zero" |
| 21 | |
| 22 | errInvalidSpanIDLength errorConst = "hex encoded span-id must have length equals to 16" |
| 23 | errNilSpanID errorConst = "span-id can't be all zero" |
| 24 | ) |
| 25 | |
| 26 | type errorConst string |
| 27 | |
| 28 | func (e errorConst) Error() string { |
| 29 | return string(e) |
| 30 | } |
| 31 | |
| 32 | // TraceID is a unique identity of a trace. |
| 33 | // nolint:revive // revive complains about stutter of `trace.TraceID`. |
| 34 | type TraceID [16]byte |
| 35 | |
| 36 | var ( |
| 37 | nilTraceID TraceID |
| 38 | _ json.Marshaler = nilTraceID |
| 39 | ) |
| 40 | |
| 41 | // IsValid checks whether the trace TraceID is valid. A valid trace ID does |
| 42 | // not consist of zeros only. |
| 43 | func (t TraceID) IsValid() bool { |
| 44 | return !bytes.Equal(t[:], nilTraceID[:]) |
| 45 | } |
| 46 | |
| 47 | // MarshalJSON implements a custom marshal function to encode TraceID |
| 48 | // as a hex string. |
| 49 | func (t TraceID) MarshalJSON() ([]byte, error) { |
| 50 | return json.Marshal(t.String()) |
| 51 | } |
| 52 | |
| 53 | // String returns the hex string representation form of a TraceID. |
| 54 | func (t TraceID) String() string { |
| 55 | return hex.EncodeToString(t[:]) |
| 56 | } |
| 57 | |
| 58 | // SpanID is a unique identity of a span in a trace. |
| 59 | type SpanID [8]byte |
| 60 | |
| 61 | var ( |
| 62 | nilSpanID SpanID |
| 63 | _ json.Marshaler = nilSpanID |
| 64 | ) |
| 65 | |
| 66 | // IsValid checks whether the SpanID is valid. A valid SpanID does not consist |
| 67 | // of zeros only. |
| 68 | func (s SpanID) IsValid() bool { |
| 69 | return !bytes.Equal(s[:], nilSpanID[:]) |
| 70 | } |
| 71 | |
| 72 | // MarshalJSON implements a custom marshal function to encode SpanID |
| 73 | // as a hex string. |
| 74 | func (s SpanID) MarshalJSON() ([]byte, error) { |
| 75 | return json.Marshal(s.String()) |
| 76 | } |
| 77 | |
| 78 | // String returns the hex string representation form of a SpanID. |
| 79 | func (s SpanID) String() string { |
| 80 | return hex.EncodeToString(s[:]) |
| 81 | } |
| 82 | |
| 83 | // TraceIDFromHex returns a TraceID from a hex string if it is compliant with |
| 84 | // the W3C trace-context specification. See more at |
| 85 | // https://www.w3.org/TR/trace-context/#trace-id |
| 86 | // nolint:revive // revive complains about stutter of `trace.TraceIDFromHex`. |
| 87 | func TraceIDFromHex(h string) (TraceID, error) { |
| 88 | t := TraceID{} |
| 89 | if len(h) != 32 { |
| 90 | return t, errInvalidTraceIDLength |
| 91 | } |
| 92 | |
| 93 | if err := decodeHex(h, t[:]); err != nil { |
| 94 | return t, err |
| 95 | } |
| 96 | |
| 97 | if !t.IsValid() { |
| 98 | return t, errNilTraceID |
| 99 | } |
| 100 | return t, nil |
| 101 | } |
| 102 | |
| 103 | // SpanIDFromHex returns a SpanID from a hex string if it is compliant |
| 104 | // with the w3c trace-context specification. |
| 105 | // See more at https://www.w3.org/TR/trace-context/#parent-id |
| 106 | func SpanIDFromHex(h string) (SpanID, error) { |
| 107 | s := SpanID{} |
| 108 | if len(h) != 16 { |
| 109 | return s, errInvalidSpanIDLength |
| 110 | } |
| 111 | |
| 112 | if err := decodeHex(h, s[:]); err != nil { |
| 113 | return s, err |
| 114 | } |
| 115 | |
| 116 | if !s.IsValid() { |
| 117 | return s, errNilSpanID |
| 118 | } |
| 119 | return s, nil |
| 120 | } |
| 121 | |
| 122 | func decodeHex(h string, b []byte) error { |
| 123 | for _, r := range h { |
| 124 | switch { |
| 125 | case 'a' <= r && r <= 'f': |
| 126 | continue |
| 127 | case '0' <= r && r <= '9': |
| 128 | continue |
| 129 | default: |
| 130 | return errInvalidHexID |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | decoded, err := hex.DecodeString(h) |
| 135 | if err != nil { |
| 136 | return err |
| 137 | } |
| 138 | |
| 139 | copy(b, decoded) |
| 140 | return nil |
| 141 | } |
| 142 | |
| 143 | // TraceFlags contains flags that can be set on a SpanContext. |
| 144 | type TraceFlags byte //nolint:revive // revive complains about stutter of `trace.TraceFlags`. |
| 145 | |
| 146 | // IsSampled returns if the sampling bit is set in the TraceFlags. |
| 147 | func (tf TraceFlags) IsSampled() bool { |
| 148 | return tf&FlagsSampled == FlagsSampled |
| 149 | } |
| 150 | |
| 151 | // WithSampled sets the sampling bit in a new copy of the TraceFlags. |
| 152 | func (tf TraceFlags) WithSampled(sampled bool) TraceFlags { // nolint:revive // sampled is not a control flag. |
| 153 | if sampled { |
| 154 | return tf | FlagsSampled |
| 155 | } |
| 156 | |
| 157 | return tf &^ FlagsSampled |
| 158 | } |
| 159 | |
| 160 | // MarshalJSON implements a custom marshal function to encode TraceFlags |
| 161 | // as a hex string. |
| 162 | func (tf TraceFlags) MarshalJSON() ([]byte, error) { |
| 163 | return json.Marshal(tf.String()) |
| 164 | } |
| 165 | |
| 166 | // String returns the hex string representation form of TraceFlags. |
| 167 | func (tf TraceFlags) String() string { |
| 168 | return hex.EncodeToString([]byte{byte(tf)}[:]) |
| 169 | } |
| 170 | |
| 171 | // SpanContextConfig contains mutable fields usable for constructing |
| 172 | // an immutable SpanContext. |
| 173 | type SpanContextConfig struct { |
| 174 | TraceID TraceID |
| 175 | SpanID SpanID |
| 176 | TraceFlags TraceFlags |
| 177 | TraceState TraceState |
| 178 | Remote bool |
| 179 | } |
| 180 | |
| 181 | // NewSpanContext constructs a SpanContext using values from the provided |
| 182 | // SpanContextConfig. |
| 183 | func NewSpanContext(config SpanContextConfig) SpanContext { |
| 184 | return SpanContext{ |
| 185 | traceID: config.TraceID, |
| 186 | spanID: config.SpanID, |
| 187 | traceFlags: config.TraceFlags, |
| 188 | traceState: config.TraceState, |
| 189 | remote: config.Remote, |
| 190 | } |
| 191 | } |
| 192 | |
| 193 | // SpanContext contains identifying trace information about a Span. |
| 194 | type SpanContext struct { |
| 195 | traceID TraceID |
| 196 | spanID SpanID |
| 197 | traceFlags TraceFlags |
| 198 | traceState TraceState |
| 199 | remote bool |
| 200 | } |
| 201 | |
| 202 | var _ json.Marshaler = SpanContext{} |
| 203 | |
| 204 | // IsValid returns if the SpanContext is valid. A valid span context has a |
| 205 | // valid TraceID and SpanID. |
| 206 | func (sc SpanContext) IsValid() bool { |
| 207 | return sc.HasTraceID() && sc.HasSpanID() |
| 208 | } |
| 209 | |
| 210 | // IsRemote indicates whether the SpanContext represents a remotely-created Span. |
| 211 | func (sc SpanContext) IsRemote() bool { |
| 212 | return sc.remote |
| 213 | } |
| 214 | |
| 215 | // WithRemote returns a copy of sc with the Remote property set to remote. |
| 216 | func (sc SpanContext) WithRemote(remote bool) SpanContext { |
| 217 | return SpanContext{ |
| 218 | traceID: sc.traceID, |
| 219 | spanID: sc.spanID, |
| 220 | traceFlags: sc.traceFlags, |
| 221 | traceState: sc.traceState, |
| 222 | remote: remote, |
| 223 | } |
| 224 | } |
| 225 | |
| 226 | // TraceID returns the TraceID from the SpanContext. |
| 227 | func (sc SpanContext) TraceID() TraceID { |
| 228 | return sc.traceID |
| 229 | } |
| 230 | |
| 231 | // HasTraceID checks if the SpanContext has a valid TraceID. |
| 232 | func (sc SpanContext) HasTraceID() bool { |
| 233 | return sc.traceID.IsValid() |
| 234 | } |
| 235 | |
| 236 | // WithTraceID returns a new SpanContext with the TraceID replaced. |
| 237 | func (sc SpanContext) WithTraceID(traceID TraceID) SpanContext { |
| 238 | return SpanContext{ |
| 239 | traceID: traceID, |
| 240 | spanID: sc.spanID, |
| 241 | traceFlags: sc.traceFlags, |
| 242 | traceState: sc.traceState, |
| 243 | remote: sc.remote, |
| 244 | } |
| 245 | } |
| 246 | |
| 247 | // SpanID returns the SpanID from the SpanContext. |
| 248 | func (sc SpanContext) SpanID() SpanID { |
| 249 | return sc.spanID |
| 250 | } |
| 251 | |
| 252 | // HasSpanID checks if the SpanContext has a valid SpanID. |
| 253 | func (sc SpanContext) HasSpanID() bool { |
| 254 | return sc.spanID.IsValid() |
| 255 | } |
| 256 | |
| 257 | // WithSpanID returns a new SpanContext with the SpanID replaced. |
| 258 | func (sc SpanContext) WithSpanID(spanID SpanID) SpanContext { |
| 259 | return SpanContext{ |
| 260 | traceID: sc.traceID, |
| 261 | spanID: spanID, |
| 262 | traceFlags: sc.traceFlags, |
| 263 | traceState: sc.traceState, |
| 264 | remote: sc.remote, |
| 265 | } |
| 266 | } |
| 267 | |
| 268 | // TraceFlags returns the flags from the SpanContext. |
| 269 | func (sc SpanContext) TraceFlags() TraceFlags { |
| 270 | return sc.traceFlags |
| 271 | } |
| 272 | |
| 273 | // IsSampled returns if the sampling bit is set in the SpanContext's TraceFlags. |
| 274 | func (sc SpanContext) IsSampled() bool { |
| 275 | return sc.traceFlags.IsSampled() |
| 276 | } |
| 277 | |
| 278 | // WithTraceFlags returns a new SpanContext with the TraceFlags replaced. |
| 279 | func (sc SpanContext) WithTraceFlags(flags TraceFlags) SpanContext { |
| 280 | return SpanContext{ |
| 281 | traceID: sc.traceID, |
| 282 | spanID: sc.spanID, |
| 283 | traceFlags: flags, |
| 284 | traceState: sc.traceState, |
| 285 | remote: sc.remote, |
| 286 | } |
| 287 | } |
| 288 | |
| 289 | // TraceState returns the TraceState from the SpanContext. |
| 290 | func (sc SpanContext) TraceState() TraceState { |
| 291 | return sc.traceState |
| 292 | } |
| 293 | |
| 294 | // WithTraceState returns a new SpanContext with the TraceState replaced. |
| 295 | func (sc SpanContext) WithTraceState(state TraceState) SpanContext { |
| 296 | return SpanContext{ |
| 297 | traceID: sc.traceID, |
| 298 | spanID: sc.spanID, |
| 299 | traceFlags: sc.traceFlags, |
| 300 | traceState: state, |
| 301 | remote: sc.remote, |
| 302 | } |
| 303 | } |
| 304 | |
| 305 | // Equal is a predicate that determines whether two SpanContext values are equal. |
| 306 | func (sc SpanContext) Equal(other SpanContext) bool { |
| 307 | return sc.traceID == other.traceID && |
| 308 | sc.spanID == other.spanID && |
| 309 | sc.traceFlags == other.traceFlags && |
| 310 | sc.traceState.String() == other.traceState.String() && |
| 311 | sc.remote == other.remote |
| 312 | } |
| 313 | |
| 314 | // MarshalJSON implements a custom marshal function to encode a SpanContext. |
| 315 | func (sc SpanContext) MarshalJSON() ([]byte, error) { |
| 316 | return json.Marshal(SpanContextConfig{ |
| 317 | TraceID: sc.traceID, |
| 318 | SpanID: sc.spanID, |
| 319 | TraceFlags: sc.traceFlags, |
| 320 | TraceState: sc.traceState, |
| 321 | Remote: sc.remote, |
| 322 | }) |
| 323 | } |