-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat(core): Add scope attribute APIs #18165
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+695
−9
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
d47204c
feat(core): Add `Scope::SetAttribute(s)` APIs
Lms24 227ebc7
add attributes.ts
Lms24 a1e1fcc
add removeAttribute method
Lms24 d72787c
fix lint
Lms24 9b4e65c
fix size limit
Lms24 cadc629
fix remove scope listener test
Lms24 c4e9005
be extra careful with typed attribute values
Lms24 29e0b2f
ref(attribute): Add type to accept `unknown` and official attribute t…
s1gr1d 8d04d23
attribute objects w/o type, keep attributes as-is on scope
Lms24 4540a35
adapt `attributeValueToTypedAttributeValue`
Lms24 994f487
ignore invalid units
Lms24 8cc1c2a
streamline isAttributeObject
Lms24 779e053
streamline primitive attribute value conversion
Lms24 7d69b12
use MeasurementUnit (aka Relay's `MetricUnit`)
Lms24 ab75d4a
use metric units without custom and none units
Lms24 bcb5925
lint
Lms24 04d75b1
save a few more bytes in `getPrimitiveType`
Lms24 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| import { DEBUG_BUILD } from './debug-build'; | ||
| import type { DurationUnit, FractionUnit, InformationUnit } from './types-hoist/measurement'; | ||
| import { debug } from './utils/debug-logger'; | ||
|
|
||
| export type RawAttributes<T> = T & ValidatedAttributes<T>; | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| export type RawAttribute<T> = T extends { value: any } | { unit: any } ? AttributeObject : T; | ||
|
|
||
| export type Attributes = Record<string, TypedAttributeValue>; | ||
|
|
||
| export type AttributeValueType = string | number | boolean | Array<string> | Array<number> | Array<boolean>; | ||
|
|
||
| type AttributeTypeMap = { | ||
| string: string; | ||
| integer: number; | ||
| double: number; | ||
| boolean: boolean; | ||
| 'string[]': Array<string>; | ||
| 'integer[]': Array<number>; | ||
| 'double[]': Array<number>; | ||
| 'boolean[]': Array<boolean>; | ||
| }; | ||
|
|
||
| /* Generates a type from the AttributeTypeMap like: | ||
| | { value: string; type: 'string' } | ||
| | { value: number; type: 'integer' } | ||
| | { value: number; type: 'double' } | ||
| */ | ||
| type AttributeUnion = { | ||
| [K in keyof AttributeTypeMap]: { | ||
| value: AttributeTypeMap[K]; | ||
| type: K; | ||
| }; | ||
| }[keyof AttributeTypeMap]; | ||
|
|
||
| export type TypedAttributeValue = AttributeUnion & { unit?: AttributeUnit }; | ||
|
|
||
| export type AttributeObject = { | ||
| value: unknown; | ||
| unit?: AttributeUnit; | ||
| }; | ||
|
|
||
| // Unfortunately, we loose type safety if we did something like Exclude<MeasurementUnit, string> | ||
| // so therefore we unionize between the three supported unit categories. | ||
| type AttributeUnit = DurationUnit | InformationUnit | FractionUnit; | ||
|
|
||
| /* If an attribute has either a 'value' or 'unit' property, we use the ValidAttributeObject type. */ | ||
| export type ValidatedAttributes<T> = { | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| [K in keyof T]: T[K] extends { value: any } | { unit: any } ? AttributeObject : unknown; | ||
| }; | ||
|
|
||
| /** | ||
| * Type-guard: The attribute object has the shape the official attribute object (value, type, unit). | ||
| * https://develop.sentry.dev/sdk/telemetry/scopes/#setting-attributes | ||
| */ | ||
| export function isAttributeObject(maybeObj: unknown): maybeObj is AttributeObject { | ||
| return ( | ||
| typeof maybeObj === 'object' && | ||
| maybeObj != null && | ||
| !Array.isArray(maybeObj) && | ||
| Object.keys(maybeObj).includes('value') | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Converts an attribute value to a typed attribute value. | ||
| * | ||
| * Does not allow mixed arrays. In case of a mixed array, the value is stringified and the type is 'string'. | ||
| * All values besides the supported attribute types (see {@link AttributeTypeMap}) are stringified to a string attribute value. | ||
| * | ||
| * @param value - The value of the passed attribute. | ||
| * @returns The typed attribute. | ||
| */ | ||
| export function attributeValueToTypedAttributeValue(rawValue: unknown): TypedAttributeValue { | ||
| const { value, unit } = isAttributeObject(rawValue) ? rawValue : { value: rawValue, unit: undefined }; | ||
| return { ...getTypedAttributeValue(value), ...(unit && typeof unit === 'string' ? { unit } : {}) }; | ||
| } | ||
|
|
||
| // Only allow string, boolean, or number types | ||
| const getPrimitiveType: ( | ||
| item: unknown, | ||
| ) => keyof Pick<AttributeTypeMap, 'string' | 'integer' | 'double' | 'boolean'> | null = item => | ||
| typeof item === 'string' | ||
| ? 'string' | ||
| : typeof item === 'boolean' | ||
| ? 'boolean' | ||
| : typeof item === 'number' && !Number.isNaN(item) | ||
| ? Number.isInteger(item) | ||
| ? 'integer' | ||
| : 'double' | ||
| : null; | ||
|
|
||
| function getTypedAttributeValue(value: unknown): TypedAttributeValue { | ||
| const primitiveType = getPrimitiveType(value); | ||
| if (primitiveType) { | ||
| // @ts-expect-error - TS complains because {@link TypedAttributeValue} is strictly typed to | ||
| // avoid setting the wrong `type` on the attribute value. | ||
| // In this case, getPrimitiveType already does the check but TS doesn't know that. | ||
| // The "clean" alternative is to return an object per `typeof value` case | ||
| // but that would require more bundle size | ||
| // Therefore, we ignore it. | ||
| return { value, type: primitiveType }; | ||
| } | ||
|
|
||
| if (Array.isArray(value)) { | ||
| const coherentArrayType = value.reduce((acc: 'string' | 'boolean' | 'integer' | 'double' | null, item) => { | ||
| if (!acc || getPrimitiveType(item) !== acc) { | ||
| return null; | ||
| } | ||
| return acc; | ||
| }, getPrimitiveType(value[0])); | ||
Lms24 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| if (coherentArrayType) { | ||
| return { value, type: `${coherentArrayType}[]` }; | ||
| } | ||
| } | ||
|
|
||
| // Fallback: stringify the passed value | ||
| let fallbackValue = ''; | ||
| try { | ||
| fallbackValue = JSON.stringify(value) ?? String(value); | ||
| } catch { | ||
| try { | ||
| fallbackValue = String(value); | ||
| } catch { | ||
| DEBUG_BUILD && debug.warn('Failed to stringify attribute value', value); | ||
| // ignore | ||
Lms24 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
|
|
||
| // This is quite a low-quality message but we cannot safely log the original `value` | ||
| // here due to String() or JSON.stringify() potentially throwing. | ||
| DEBUG_BUILD && | ||
| debug.log(`Stringified attribute value to ${fallbackValue} because it's not a supported attribute value type`); | ||
|
|
||
| return { | ||
| value: fallbackValue, | ||
| type: 'string', | ||
| }; | ||
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In contrast to logs and metrics attribute definitions and helper functions, this one already handles array attributes as well as attributes with units. My thinking is, we introduce this here then unify logs, metrics (+ spans eventually) to use the types and APIs here. While we'll have to change the used types for logs and metrics, I don't think this is breaking as theoretically logs and metrics support
unknownfor attribute values.