opentelemetry

opentelemetry

OpenTelemetry observability - use for distributed tracing, metrics, instrumentation, Sentry integration, and monitoring

1stars
0forks
Updated 12/8/2025
SKILL.md
readonlyread-only
name
opentelemetry
description

OpenTelemetry observability - use for distributed tracing, metrics, instrumentation, Sentry integration, and monitoring

OpenTelemetry Patterns

Spring Boot Configuration

// 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")
}
# 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

Custom Span Creation

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

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

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

// 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()
  }
}
// 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/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

// 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

# 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]

You Might Also Like

Related Skills

create-pr

create-pr

170Kdev-devops

Creates GitHub pull requests with properly formatted titles that pass the check-pr-title CI validation. Use when creating PRs, submitting changes for review, or when the user says /pr or asks to create a pull request.

n8n-io avatarn8n-io
Get

Guide for performing Chromium version upgrades in the Electron project. Use when working on the roller/chromium/main branch to fix patch conflicts during `e sync --3`. Covers the patch application workflow, conflict resolution, analyzing upstream Chromium changes, and proper commit formatting for patch fixes.

electron avatarelectron
Get
pr-creator

pr-creator

92Kdev-devops

Use this skill when asked to create a pull request (PR). It ensures all PRs follow the repository's established templates and standards.

google-gemini avatargoogle-gemini
Get
clawdhub

clawdhub

87Kdev-devops

Use the ClawdHub CLI to search, install, update, and publish agent skills from clawdhub.com. Use when you need to fetch new skills on the fly, sync installed skills to latest or a specific version, or publish new/updated skill folders with the npm-installed clawdhub CLI.

moltbot avatarmoltbot
Get
tmux

tmux

87Kdev-devops

Remote-control tmux sessions for interactive CLIs by sending keystrokes and scraping pane output.

moltbot avatarmoltbot
Get
create-pull-request

create-pull-request

57Kdev-devops

Create a GitHub pull request following project conventions. Use when the user asks to create a PR, submit changes for review, or open a pull request. Handles commit analysis, branch management, and PR creation using the gh CLI tool.

cline avatarcline
Get