All files / packages/telemetry/src/semconv common.ts

86.36% Statements 19/22
66.66% Branches 12/18
100% Functions 3/3
88.88% Lines 16/18

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88                                        6x       6x 6x   6x                 2079x 36x       36x 36x                   2x                                 13x 1x             12x 1x     11x 9x     2x    
import type { Attributes } from '@opentelemetry/api'
import {
	ATTR_DB_NAMESPACE,
	ATTR_DB_RESPONSE_STATUS_CODE,
	ATTR_DB_SYSTEM_NAME,
	ATTR_ERROR_TYPE,
	ATTR_SERVER_ADDRESS,
	ATTR_SERVER_PORT,
} from '@opentelemetry/semantic-conventions'
 
import type { StatusIds_StatusCode } from '@ydbjs/api/operation'
import type { DriverIdentity } from '@ydbjs/core'
import { YDBError } from '@ydbjs/error'
import { ClientError } from 'nice-grpc'
 
// Keys used by BOTH pipelines live here. Span-only identifiers go in
// `spans.ts`; metric-only tag enums go in `metrics.ts`.
 
export type BaseAttributes = Attributes & { [ATTR_DB_SYSTEM_NAME]: 'ydb' }
 
export let BASE_ATTRIBUTES: BaseAttributes = { [ATTR_DB_SYSTEM_NAME]: 'ydb' }
 
// Dot-separated to match other YDB SDKs (ydb-dotnet, ydb-go); don't
// introduce snake_case variants of the same concept.
export let ATTR_YDB_NODE_ID = 'ydb.node.id'
export let ATTR_YDB_NODE_DC = 'ydb.node.dc'
// Bridge (2DC) pile the node belongs to; '' / absent on a non-bridge cluster.
export let ATTR_YDB_NODE_PILE = 'ydb.node.pile'
 
/**
 * Per-driver identity attributes (`db.namespace`, `server.address`,
 * `server.port`). Returns an empty object when no identity is present so
 * callers can spread the result unconditionally. Used by both the traces
 * and the metrics pipeline.
 */
export function identityAttrs(driver: DriverIdentity | undefined): Attributes {
	if (!driver) return {}
	let attrs: Attributes = {
		[ATTR_DB_NAMESPACE]: driver.database,
		[ATTR_SERVER_ADDRESS]: driver.address,
	}
	Eif (driver.port !== undefined) attrs[ATTR_SERVER_PORT] = driver.port
	return attrs
}
 
/**
 * Coerce an arbitrary thrown value into an `Error` suitable for
 * `span.recordException`. We do NOT stringify the value via its own
 * `toString` because a custom implementation could leak secrets (tokens,
 * credentials) into the recorded exception.
 */
export function coerceError(value: unknown): Error {
	Eif (value instanceof Error) return value
	if (typeof value === 'string') return new Error(value)
	return new Error('non-Error throw')
}
 
/**
 * Error categories mirror ydb-dotnet:
 *   - `ydb_error`       — server returned a YDB status code
 *   - `transport_error` — gRPC client/transport failure (no server response)
 *   - error.name        — anything else (AbortError, TimeoutError, ...)
 *
 * `db.response.status_code` is set only in the first category.
 */
export function recordErrorAttributes(error: unknown): {
	[ATTR_DB_RESPONSE_STATUS_CODE]?: string
	[ATTR_ERROR_TYPE]: string
} {
	if (error instanceof YDBError) {
		return {
			[ATTR_DB_RESPONSE_STATUS_CODE]:
				YDBError.codes[error.code as StatusIds_StatusCode] ?? 'STATUS_CODE_UNSPECIFIED',
			[ATTR_ERROR_TYPE]: 'ydb_error',
		}
	}
 
	if (error instanceof ClientError) {
		return { [ATTR_ERROR_TYPE]: 'transport_error' }
	}
 
	if (error instanceof Error) {
		return { [ATTR_ERROR_TYPE]: error.name || 'Error' }
	}
 
	return { [ATTR_ERROR_TYPE]: 'unknown' }
}