opentelemetry
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseOpenTelemetry Patterns
OpenTelemetry 实践模式
Spring Boot Configuration
Spring Boot 配置
kotlin
// build.gradle.kts
dependencies {
implementation(platform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom:2.15.0"))
implementation("io.opentelemetry.instrumentation:opentelemetry-spring-boot-starter")
implementation("io.micrometer:micrometer-tracing-bridge-otel")
implementation("io.opentelemetry:opentelemetry-exporter-zipkin")
// Sentry integration
implementation("io.sentry:sentry-spring-boot-starter-jakarta:8.26.0")
implementation("io.sentry:sentry-logback:8.26.0")
}yaml
undefinedkotlin
// build.gradle.kts
dependencies {
implementation(platform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom:2.15.0"))
implementation("io.opentelemetry.instrumentation:opentelemetry-spring-boot-starter")
implementation("io.micrometer:micrometer-tracing-bridge-otel")
implementation("io.opentelemetry:opentelemetry-exporter-zipkin")
// Sentry integration
implementation("io.sentry:sentry-spring-boot-starter-jakarta:8.26.0")
implementation("io.sentry:sentry-logback:8.26.0")
}yaml
undefinedapplication.yaml
application.yaml
spring:
application:
name: orca-facade
management:
tracing:
sampling:
probability: 1.0 # 100% in dev, lower in prod
otlp:
tracing:
endpoint: http://localhost:4318/v1/traces
otel:
exporter:
otlp:
endpoint: http://otel-collector:4317
service:
name: orca-facade
resource:
attributes:
deployment.environment: ${ENVIRONMENT:dev}
service.version: ${APP_VERSION:unknown}
sentry:
dsn: ${SENTRY_DSN:}
environment: ${ENVIRONMENT:dev}
traces-sample-rate: 1.0
undefinedspring:
application:
name: orca-facade
management:
tracing:
sampling:
probability: 1.0 # 100% in dev, lower in prod
otlp:
tracing:
endpoint: http://localhost:4318/v1/traces
otel:
exporter:
otlp:
endpoint: http://otel-collector:4317
service:
name: orca-facade
resource:
attributes:
deployment.environment: ${ENVIRONMENT:dev}
service.version: ${APP_VERSION:unknown}
sentry:
dsn: ${SENTRY_DSN:}
environment: ${ENVIRONMENT:dev}
traces-sample-rate: 1.0
undefinedCustom Span Creation
自定义 Span 创建
kotlin
import io.opentelemetry.api.trace.Span
import io.opentelemetry.api.trace.Tracer
import io.opentelemetry.context.Context
import org.springframework.stereotype.Component
@Component
class TracingService(
private val tracer: Tracer
) {
fun <T> withSpan(
spanName: String,
attributes: Map<String, String> = emptyMap(),
block: () -> T
): T {
val span = tracer.spanBuilder(spanName)
.setParent(Context.current())
.startSpan()
attributes.forEach { (key, value) ->
span.setAttribute(key, value)
}
return try {
span.makeCurrent().use {
block()
}
} catch (e: Exception) {
span.recordException(e)
span.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.message ?: "Error")
throw e
} finally {
span.end()
}
}
}
// Usage
@Service
class EnvironmentService(
private val tracingService: TracingService,
private val repository: EnvironmentRepository
) {
fun createEnvironment(request: CreateRequest): Environment {
return tracingService.withSpan(
"EnvironmentService.createEnvironment",
mapOf(
"environment.name" to request.name,
"user.id" to request.userId
)
) {
// Add events
Span.current().addEvent("Validating request")
validateRequest(request)
Span.current().addEvent("Saving to database")
repository.save(request.toEntity())
}
}
}kotlin
import io.opentelemetry.api.trace.Span
import io.opentelemetry.api.trace.Tracer
import io.opentelemetry.context.Context
import org.springframework.stereotype.Component
@Component
class TracingService(
private val tracer: Tracer
) {
fun <T> withSpan(
spanName: String,
attributes: Map<String, String> = emptyMap(),
block: () -> T
): T {
val span = tracer.spanBuilder(spanName)
.setParent(Context.current())
.startSpan()
attributes.forEach { (key, value) ->
span.setAttribute(key, value)
}
return try {
span.makeCurrent().use {
block()
}
} catch (e: Exception) {
span.recordException(e)
span.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, e.message ?: "Error")
throw e
} finally {
span.end()
}
}
}
// Usage
@Service
class EnvironmentService(
private val tracingService: TracingService,
private val repository: EnvironmentRepository
) {
fun createEnvironment(request: CreateRequest): Environment {
return tracingService.withSpan(
"EnvironmentService.createEnvironment",
mapOf(
"environment.name" to request.name,
"user.id" to request.userId
)
) {
// Add events
Span.current().addEvent("Validating request")
validateRequest(request)
Span.current().addEvent("Saving to database")
repository.save(request.toEntity())
}
}
}Annotation-Based Tracing
基于注解的追踪
kotlin
import io.micrometer.tracing.annotation.NewSpan
import io.micrometer.tracing.annotation.SpanTag
@Service
class ComputeService {
@NewSpan("compute.createInstance")
fun createInstance(
@SpanTag("instance.type") type: String,
@SpanTag("instance.region") region: String
): Instance {
// Automatically traced
return computeClient.create(type, region)
}
}kotlin
import io.micrometer.tracing.annotation.NewSpan
import io.micrometer.tracing.annotation.SpanTag
@Service
class ComputeService {
@NewSpan("compute.createInstance")
fun createInstance(
@SpanTag("instance.type") type: String,
@SpanTag("instance.region") region: String
): Instance {
// Automatically traced
return computeClient.create(type, region)
}
}Baggage Propagation
Baggage 上下文传递
kotlin
import io.opentelemetry.api.baggage.Baggage
// Set baggage (propagates across services)
fun setUserContext(userId: String, tenantId: String) {
Baggage.current()
.toBuilder()
.put("user.id", userId)
.put("tenant.id", tenantId)
.build()
.makeCurrent()
}
// Read baggage
fun getCurrentUserId(): String? {
return Baggage.current().getEntryValue("user.id")
}kotlin
import io.opentelemetry.api.baggage.Baggage
// Set baggage (propagates across services)
fun setUserContext(userId: String, tenantId: String) {
Baggage.current()
.toBuilder()
.put("user.id", userId)
.put("tenant.id", tenantId)
.build()
.makeCurrent()
}
// Read baggage
fun getCurrentUserId(): String? {
return Baggage.current().getEntryValue("user.id")
}Next.js / Node.js Setup
Next.js / Node.js 环境配置
typescript
// instrumentation.ts (Next.js)
import { NodeSDK } from '@opentelemetry/sdk-node'
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { Resource } from '@opentelemetry/resources'
import { SEMRESATTRS_SERVICE_NAME, SEMRESATTRS_SERVICE_VERSION } from '@opentelemetry/semantic-conventions'
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
const sdk = new NodeSDK({
resource: new Resource({
[SEMRESATTRS_SERVICE_NAME]: 'orca-lab',
[SEMRESATTRS_SERVICE_VERSION]: process.env.npm_package_version || 'unknown',
}),
traceExporter: new OTLPTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4318/v1/traces',
}),
instrumentations: [
getNodeAutoInstrumentations({
'@opentelemetry/instrumentation-fs': { enabled: false },
}),
],
})
sdk.start()
}
}typescript
// lib/tracing.ts
import { trace, SpanStatusCode, context } from '@opentelemetry/api'
const tracer = trace.getTracer('orca-lab')
export async function withSpan<T>(
name: string,
attributes: Record<string, string>,
fn: () => Promise<T>
): Promise<T> {
return tracer.startActiveSpan(name, async (span) => {
try {
Object.entries(attributes).forEach(([key, value]) => {
span.setAttribute(key, value)
})
const result = await fn()
span.setStatus({ code: SpanStatusCode.OK })
return result
} catch (error) {
span.setStatus({
code: SpanStatusCode.ERROR,
message: error instanceof Error ? error.message : 'Unknown error',
})
span.recordException(error as Error)
throw error
} finally {
span.end()
}
})
}
// Usage
export async function createEnvironment(data: CreateEnvInput) {
return withSpan(
'createEnvironment',
{ 'environment.name': data.name },
async () => {
const response = await fetch('/api/environments', {
method: 'POST',
body: JSON.stringify(data),
})
return response.json()
}
)
}typescript
// instrumentation.ts (Next.js)
import { NodeSDK } from '@opentelemetry/sdk-node'
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { Resource } from '@opentelemetry/resources'
import { SEMRESATTRS_SERVICE_NAME, SEMRESATTRS_SERVICE_VERSION } from '@opentelemetry/semantic-conventions'
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
const sdk = new NodeSDK({
resource: new Resource({
[SEMRESATTRS_SERVICE_NAME]: 'orca-lab',
[SEMRESATTRS_SERVICE_VERSION]: process.env.npm_package_version || 'unknown',
}),
traceExporter: new OTLPTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4318/v1/traces',
}),
instrumentations: [
getNodeAutoInstrumentations({
'@opentelemetry/instrumentation-fs': { enabled: false },
}),
],
})
sdk.start()
}
}typescript
// lib/tracing.ts
import { trace, SpanStatusCode, context } from '@opentelemetry/api'
const tracer = trace.getTracer('orca-lab')
export async function withSpan<T>(
name: string,
attributes: Record<string, string>,
fn: () => Promise<T>
): Promise<T> {
return tracer.startActiveSpan(name, async (span) => {
try {
Object.entries(attributes).forEach(([key, value]) => {
span.setAttribute(key, value)
})
const result = await fn()
span.setStatus({ code: SpanStatusCode.OK })
return result
} catch (error) {
span.setStatus({
code: SpanStatusCode.ERROR,
message: error instanceof Error ? error.message : 'Unknown error',
})
span.recordException(error as Error)
throw error
} finally {
span.end()
}
})
}
// Usage
export async function createEnvironment(data: CreateEnvInput) {
return withSpan(
'createEnvironment',
{ 'environment.name': data.name },
async () => {
const response = await fetch('/api/environments', {
method: 'POST',
body: JSON.stringify(data),
})
return response.json()
}
)
}Metrics
指标监控
kotlin
// Kotlin/Spring Boot
import io.micrometer.core.instrument.MeterRegistry
import io.micrometer.core.instrument.Timer
@Component
class MetricsService(
private val registry: MeterRegistry
) {
private val environmentCreatedCounter = registry.counter(
"orca.environment.created",
"type", "standard"
)
private val environmentCreationTimer = Timer.builder("orca.environment.creation.duration")
.description("Time to create an environment")
.register(registry)
fun recordEnvironmentCreated(type: String) {
registry.counter("orca.environment.created", "type", type).increment()
}
fun <T> timeEnvironmentCreation(block: () -> T): T {
return environmentCreationTimer.recordCallable(block)!!
}
}kotlin
// Kotlin/Spring Boot
import io.micrometer.core.instrument.MeterRegistry
import io.micrometer.core.instrument.Timer
@Component
class MetricsService(
private val registry: MeterRegistry
) {
private val environmentCreatedCounter = registry.counter(
"orca.environment.created",
"type", "standard"
)
private val environmentCreationTimer = Timer.builder("orca.environment.creation.duration")
.description("Time to create an environment")
.register(registry)
fun recordEnvironmentCreated(type: String) {
registry.counter("orca.environment.created", "type", type).increment()
}
fun <T> timeEnvironmentCreation(block: () -> T): T {
return environmentCreationTimer.recordCallable(block)!!
}
}Sentry Integration
Sentry 集成
kotlin
// Error reporting with Sentry
import io.sentry.Sentry
import io.sentry.SentryLevel
@ControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(Exception::class)
fun handleException(e: Exception): ResponseEntity<ErrorResponse> {
// Report to Sentry with context
Sentry.withScope { scope ->
scope.setTag("error.type", e.javaClass.simpleName)
scope.setLevel(SentryLevel.ERROR)
scope.setContexts("request", mapOf(
"path" to getCurrentRequestPath(),
"method" to getCurrentRequestMethod()
))
Sentry.captureException(e)
}
return ResponseEntity.status(500)
.body(ErrorResponse("Internal server error"))
}
}kotlin
// Error reporting with Sentry
import io.sentry.Sentry
import io.sentry.SentryLevel
@ControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(Exception::class)
fun handleException(e: Exception): ResponseEntity<ErrorResponse> {
// Report to Sentry with context
Sentry.withScope { scope ->
scope.setTag("error.type", e.javaClass.simpleName)
scope.setLevel(SentryLevel.ERROR)
scope.setContexts("request", mapOf(
"path" to getCurrentRequestPath(),
"method" to getCurrentRequestMethod()
))
Sentry.captureException(e)
}
return ResponseEntity.status(500)
.body(ErrorResponse("Internal server error"))
}
}OpenTelemetry Collector Config
OpenTelemetry Collector 配置
yaml
undefinedyaml
undefinedotel-collector-config.yaml
otel-collector-config.yaml
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
timeout: 1s
send_batch_size: 1024
exporters:
zipkin:
endpoint: http://zipkin:9411/api/v2/spans
prometheus:
endpoint: 0.0.0.0:8889
logging:
loglevel: debug
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [zipkin, logging]
metrics:
receivers: [otlp]
processors: [batch]
exporters: [prometheus]
undefinedreceivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
timeout: 1s
send_batch_size: 1024
exporters:
zipkin:
endpoint: http://zipkin:9411/api/v2/spans
prometheus:
endpoint: 0.0.0.0:8889
logging:
loglevel: debug
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [zipkin, logging]
metrics:
receivers: [otlp]
processors: [batch]
exporters: [prometheus]
undefined