/** * Builds OTLP resource metadata shared by exported telemetry. * * An OTLP resource describes the service and other attributes attached to every * exported log, metric, or trace. This module builds resources from explicit * options or OpenTelemetry environment variables and converts JavaScript values * into OTLP attribute values. * * @stability unstable * @since 4.0.0 */ import * as Config from "../Config.ts" import * as Effect from "../Effect.ts" import { format } from "../Formatter.ts" import * as Schema from "../Schema.ts" import * as Version from "../Version.ts" /** * OTLP resource metadata attached to exported logs, metrics, and traces. * * @stability unstable * @category models * @since 4.0.0 */ export interface Resource { /** Resource attributes */ attributes: Array /** Resource droppedAttributesCount */ droppedAttributesCount: number } const isBrowser = "window" in globalThis || "importScripts" in globalThis /** * Creates an OTLP resource from service metadata and additional attributes. * * **Details** * * Adds `telemetry.sdk.*` defaults and converts custom attributes to OTLP values. * Custom attributes override SDK defaults; service options override matching * attributes. Omitted service options add no service attributes. * * @stability unstable * @category constructors * @since 4.0.0 */ export const make = (options: { readonly serviceName?: string | undefined readonly serviceVersion?: string | undefined readonly attributes?: Record | undefined }): Resource => ({ attributes: entriesToAttributes(Object.entries({ "telemetry.sdk.name": "effect", "telemetry.sdk.language": isBrowser ? "webjs" : "nodejs", "telemetry.sdk.version": Version.getCurrentVersion(), ...options.attributes, ...(options.serviceName ? { "service.name": options.serviceName } : undefined), ...(options.serviceVersion ? { "service.version": options.serviceVersion } : undefined) })), droppedAttributesCount: 0 }) /** * Creates an OTLP resource from explicit options and OpenTelemetry * configuration. * * **When to use** * * Use when resource metadata may be configured in code or by the deployment * environment. To let operators set the service identity, omit `serviceName`, * `serviceVersion`, and their matching attributes, then use * `OTEL_SERVICE_NAME` and `OTEL_RESOURCE_ATTRIBUTES`. * * **Details** * * Explicit `serviceName` and `serviceVersion` options take precedence over * matching explicit attributes. Explicit attributes take precedence over * environment variables. `OTEL_SERVICE_NAME` and `OTEL_SERVICE_VERSION` take * precedence over matching attributes in `OTEL_RESOURCE_ATTRIBUTES`. Missing * required configuration is converted to a defect. * * @stability unstable * @category constructors * @since 4.0.0 */ export const fromConfig: ( options?: { readonly serviceName?: string | undefined readonly serviceVersion?: string | undefined readonly attributes?: Record | undefined } | undefined ) => Effect.Effect = Effect.fnUntraced(function*(options?: { readonly serviceName?: string | undefined readonly serviceVersion?: string | undefined readonly attributes?: Record | undefined }) { const env = yield* Config.Record( Schema.StringFromUriComponent, Schema.StringFromUriComponent, "OTEL_RESOURCE_ATTRIBUTES" ).pipe(Config.withDefault(undefined)) const serviceName = options?.serviceName ?? options?.attributes?.["service.name"] as string | undefined ?? (yield* Config.schema(Schema.UndefinedOr(Schema.String), "OTEL_SERVICE_NAME")) ?? env?.["service.name"] as string | undefined ?? (yield* Config.String("OTEL_SERVICE_NAME")) const serviceVersion = options?.serviceVersion ?? options?.attributes?.["service.version"] as string | undefined ?? (yield* Config.schema(Schema.UndefinedOr(Schema.String), "OTEL_SERVICE_VERSION")) ?? env?.["service.version"] as string | undefined const attributes = { ...env, ...options?.attributes } delete attributes["service.name"] delete attributes["service.version"] return make({ serviceName, serviceVersion, attributes }) }, Effect.orDie) /** * Returns the `service.name` attribute from an OTLP resource. * * **When to use** * * Use when an OTLP resource is known to contain a string `service.name` and * throwing is acceptable if that invariant is broken. * * **Gotchas** * * Throws if the resource does not contain a string `service.name` attribute. * * @stability unstable * @category attributes * @since 4.0.0 */ export const serviceNameUnsafe = (resource: Resource): string => { const serviceNameAttribute = resource.attributes.find( (attr) => attr.key === "service.name" ) if (!serviceNameAttribute || !serviceNameAttribute.value.stringValue) { throw new Error("Resource does not contain a service name") } return serviceNameAttribute.value.stringValue } /** * Converts key/value entries into OTLP `KeyValue` attributes. * * @stability unstable * @category attributes * @since 4.0.0 */ export const entriesToAttributes = (entries: Iterable<[string, unknown]>): Array => { const attributes: Array = [] for (const [key, value] of entries) { attributes.push({ key, value: unknownToAttributeValue(value) }) } return attributes } // OTLP `intValue` is a signed 64-bit integer const int64Bound = 2 ** 63 /** * Converts an arbitrary JavaScript value into an OTLP `AnyValue`. * * **Details** * * Arrays are converted recursively, primitive values use their matching OTLP * fields, and unsupported values are formatted as strings. Integers outside the * signed 64-bit range fall back to `doubleValue` for numbers and `stringValue` * for bigints. * * @stability unstable * @category attributes * @since 4.0.0 */ export const unknownToAttributeValue = (value: unknown): AnyValue => { if (Array.isArray(value)) { return { arrayValue: { values: value.map(unknownToAttributeValue) } } } switch (typeof value) { case "string": return { stringValue: value } case "bigint": return BigInt.asIntN(64, value) === value ? { intValue: String(value) } : { stringValue: String(value) } case "number": return Number.isInteger(value) && value >= -int64Bound && value < int64Bound ? { // Unsafe integers would be rounded when written as JSON numbers intValue: Number.isSafeInteger(value) ? value : String(BigInt(value)) } : { doubleValue: value } case "boolean": return { boolValue: value } default: return { stringValue: format(value) } } } /** * An OTLP attribute represented as a string key and typed value. * * @stability unstable * @category models * @since 4.0.0 */ export interface KeyValue { /** KeyValue key */ key: string /** KeyValue value */ value: AnyValue } /** * OTLP `AnyValue` payload for scalar, array, key/value-list, or byte values. * * @stability unstable * @category models * @since 4.0.0 */ export interface AnyValue { /** AnyValue stringValue */ stringValue?: string | null /** AnyValue boolValue */ boolValue?: boolean | null /** AnyValue intValue */ intValue?: string | number | null /** AnyValue doubleValue */ doubleValue?: number | null /** AnyValue arrayValue */ arrayValue?: ArrayValue /** AnyValue kvlistValue */ kvlistValue?: KeyValueList /** AnyValue bytesValue */ bytesValue?: Uint8Array } /** * OTLP array value containing nested `AnyValue` entries. * * @stability unstable * @category models * @since 4.0.0 */ export interface ArrayValue { /** ArrayValue values */ values: Array } /** * OTLP key/value-list value containing nested attributes. * * @stability unstable * @category models * @since 4.0.0 */ export interface KeyValueList { /** KeyValueList values */ values: Array } /** * Low and high 32-bit parts of a 64-bit integer value. * * @stability unstable * @category models * @since 4.0.0 */ export interface LongBits { low: number high: number } /** * Accepted runtime representations for an OTLP/protobuf fixed 64-bit value. * * @stability unstable * @category models * @since 4.0.0 */ export type Fixed64 = LongBits | string | number