Live:CloudOps Webinars & Hands-on Workshops ·Register ↗
본문으로 건너뛰기

예제 및 코드 샘플

샘플 애플리케이션

Application Signals 계측을 시작하는 데 도움이 되는 완전한 실행 가능 예제입니다.

간단한 Lambda 함수

자동 계측이 적용된 기본 서버리스 함수입니다.

Python 예제:

import json
import boto3
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter

# 트레이싱 초기화
trace.set_tracer_provider(TracerProvider())
tracer = trace.get_tracer(__name__)

# OTLP exporter 구성
otlp_exporter = OTLPSpanExporter(
endpoint="https://otel-collector.endpoint",
headers={"Authorization": "[REDACTED_TOKEN]"}
)
span_processor = BatchSpanProcessor(otlp_exporter)
trace.get_tracer_provider().add_span_processor(span_processor)

def lambda_handler(event, context):
with tracer.start_as_span("process-order") as span:
# 사용자 정의 속성 추가
span.set_attribute("order.id", event.get("orderId"))
span.set_attribute("customer.id", event.get("customerId"))

# 처리 시뮬레이션
order_total = calculate_total(event.get("items", []))
span.set_attribute("order.total", order_total)

# 다운스트림 서비스 호출
payment_result = process_payment(order_total)

if not payment_result["success"]:
span.set_attribute("error", True)
span.set_attribute("error.message", payment_result["message"])
raise Exception("Payment failed")

return {
"statusCode": 200,
"body": json.dumps({"orderId": event["orderId"], "status": "confirmed"})
}

def calculate_total(items):
return sum(item["price"] * item["quantity"] for item in items)

def process_payment(amount):
# 결제 처리 시뮬레이션
return {"success": True, "transactionId": "txn_123"}

API Gateway를 활용한 REST API

Express.js로 구축하고 Application Signals를 위해 계측한 REST API입니다.

Node.js 예제:

const express = require('express');
const { trace } = require('@opentelemetry/api');

const app = express();
const tracer = trace.getTracer('user-service');

app.use(express.json());

// 자동 계측 미들웨어 (OpenTelemetry 통해)
app.use((req, res, next) => {
const span = trace.getSpan(trace.getActiveContext());
if (span) {
span.setAttribute('http.method', req.method);
span.setAttribute('http.url', req.url);
span.setAttribute('user.id', req.headers['x-user-id']);
}
next();
});

app.get('/users/:id', async (req, res) => {
const span = tracer.startSpan('get-user', {
attributes: {
'user.id': req.params.id,
'operation': 'read'
}
});

try {
// 데이터베이스 호출 시뮬레이션
const user = await getUserFromDB(req.params.id);

span.setAttribute('db.rows_returned', user ? 1 : 0);
span.addEvent('user retrieved', {
'user.exists': !!user
});

res.json(user);
} catch (error) {
span.setAttribute('error', true);
span.setAttribute('error.message', error.message);
res.status(500).json({ error: 'Internal server error' });
} finally {
span.end();
}
});

app.post('/users', async (req, res) => {
const span = tracer.startSpan('create-user', {
attributes: {
'operation': 'create',
'user.email': req.body.email
}
});

try {
const user = await createUserInDB(req.body);

span.setAttribute('user.id', user.id);
span.addEvent('user created');

res.status(201).json(user);
} catch (error) {
span.setAttribute('error', true);
span.setAttribute('error.message', error.message);
res.status(400).json({ error: error.message });
} finally {
span.end();
}
});

async function getUserFromDB(userId) {
// 비동기 데이터베이스 작업 시뮬레이션
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id: userId, name: 'John Doe', email: 'john@example.com' });
}, Math.random() * 100);
});
}

async function createUserInDB(userData) {
// 사용자 생성 시뮬레이션
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id: Date.now().toString(), ...userData });
}, Math.random() * 200);
});
}

module.exports = app;

마이크로서비스 애플리케이션

서비스 간 분산 트레이싱을 보여주는 멀티 서비스 애플리케이션입니다.

Order Service (Java):

@RestController
@RequestMapping("/orders")
public class OrderController {

private final Tracer tracer;
private final WebClient webClient;

public OrderController(Tracer tracer, WebClient.Builder webClientBuilder) {
this.tracer = tracer;
this.webClient = webClientBuilder.build();
}

@PostMapping
public Mono<Order> createOrder(@RequestBody CreateOrderRequest request) {
Span span = tracer.startSpan("create-order");

try (Scope scope = span.makeCurrent()) {
span.setAttribute("order.customer_id", request.getCustomerId());
span.setAttribute("order.total", request.getTotal());

// 인벤토리 서비스 호출
return webClient.post()
.uri("http://inventory-service/reserve")
.bodyValue(new ReserveInventoryRequest(request.getItems()))
.retrieve()
.bodyToMono(InventoryReservation.class)
.flatMap(reservation -> {
span.addEvent("inventory reserved",
Attributes.of("reservation.id", reservation.getId()));

// 결제 서비스 호출
return webClient.post()
.uri("http://payment-service/charge")
.bodyValue(new PaymentRequest(request.getTotal()))
.retrieve()
.bodyToMono(PaymentResult.class)
.map(payment -> {
if (payment.isSuccess()) {
span.addEvent("payment processed");
return new Order(reservation.getId(), payment.getTransactionId());
} else {
span.setAttribute("error", true);
span.setAttribute("error.type", "payment_failed");
throw new RuntimeException("Payment failed");
}
});
});
} catch (Exception e) {
span.setAttribute("error", true);
span.setAttribute("error.message", e.getMessage());
throw e;
} finally {
span.end();
}
}
}

Inventory Service (Python):

from flask import Flask, request, jsonify
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
import boto3

app = Flask(__name__)
tracer = trace.get_tracer(__name__)

# DynamoDB 클라이언트 초기화
dynamodb = boto3.resource('dynamodb')
inventory_table = dynamodb.Table('inventory')

@app.route('/reserve', methods=['POST'])
def reserve_inventory():
with tracer.start_as_span("reserve-inventory") as span:
items = request.json.get('items', [])

span.set_attribute("inventory.items_count", len(items))

try:
# 인벤토리 확인 및 예약
reservation_id = reserve_items(items)

span.set_attribute("inventory.reservation_id", reservation_id)
span.addEvent("inventory reserved")

return jsonify({"reservationId": reservation_id})

except Exception as e:
span.set_attribute("error", True)
span.set_attribute("error.message", str(e))
return jsonify({"error": "Inventory reservation failed"}), 400

def reserve_items(items):
# 인벤토리 예약 로직 시뮬레이션
reservation_id = f"res_{hash(str(items))}"

# 실제 구현에서는 원자성을 보장하기 위해
# 조건부 쓰기로 DynamoDB를 업데이트합니다

return reservation_id

코드 스니펫

일반적인 계측 패턴에 재사용할 수 있는 코드 스니펫입니다.

사용자 정의 스팬 생성

Java:

Span span = tracer.startSpan("database-query");
try (Scope scope = span.makeCurrent()) {
span.setAttribute("db.system", "postgresql");
span.setAttribute("db.name", "userdb");
span.setAttribute("db.operation", "select");

// 쿼리 실행
ResultSet rs = statement.executeQuery("SELECT * FROM users");

span.setAttribute("db.rows_returned", rs.getFetchSize());

} catch (SQLException e) {
span.setAttribute("error", true);
span.setAttribute("error.message", e.getMessage());
throw e;
} finally {
span.end();
}

Python:

with tracer.start_as_span("database-query") as span:
span.set_attribute("db.system", "postgresql")
span.set_attribute("db.name", "userdb")
span.set_attribute("db.operation", "select")

try:
cursor.execute("SELECT * FROM users")
results = cursor.fetchall()

span.set_attribute("db.rows_returned", len(results))
span.add_event("query executed", {"row_count": len(results)})

except Exception as e:
span.set_attribute("error", True)
span.set_attribute("error.message", str(e))
raise

에러 처리

JavaScript:

const span = tracer.startSpan('process-payment');

try {
const result = await processPayment(amount);

if (!result.success) {
span.setAttribute('error', true);
span.setAttribute('error.type', 'payment_declined');
span.setAttribute('payment.amount', amount);
span.addEvent('payment failed', {
'reason': result.reason,
'decline_code': result.declineCode
});
} else {
span.setAttribute('payment.transaction_id', result.transactionId);
span.addEvent('payment succeeded');
}

} catch (error) {
span.setAttribute('error', true);
span.setAttribute('error.type', 'exception');
span.setAttribute('error.message', error.message);
span.recordException(error);
} finally {
span.end();
}

컨텍스트 전파

HTTP Headers (Java):

// 수신 요청에서 컨텍스트 추출
TextMapGetter<HttpServletRequest> getter = new TextMapGetter<>() {
@Override
public String get(HttpServletRequest carrier, String key) {
return carrier.getHeader(key);
}
};

Context context = otel.getPropagators()
.getTextMapPropagator()
.extract(Context.current(), request, getter);

// 추출된 컨텍스트 사용
Span span = tracer.startSpan("handle-request", context);

HTTP Headers (Python):

from opentelemetry.propagators.textmap import TextMapPropagator

# 헤더에서 컨텍스트 추출
carrier = dict(request.headers)
context = TextMapPropagator().extract(carrier)

# 추출된 컨텍스트로 스팬 시작
with tracer.start_as_span("handle-request", context=context) as span:
# 요청 처리
pass

비즈니스 로직 트레이싱

전자상거래 예제:

const span = tracer.startSpan('checkout-process');

span.setAttribute('checkout.session_id', sessionId);
span.setAttribute('checkout.items_count', cart.items.length);
span.setAttribute('checkout.total_amount', cart.total);

span.addEvent('checkout started', {
'user.id': userId,
'cart.value': cart.total
});

// 장바구니 검증
const validationSpan = tracer.startSpan('validate-cart', {}, span.getContext());
try {
validateCart(cart);
validationSpan.addEvent('cart validated');
} finally {
validationSpan.end();
}

// 결제 처리
const paymentSpan = tracer.startSpan('process-payment', {}, span.getContext());
try {
const payment = await processPayment(cart.total);
paymentSpan.setAttribute('payment.method', payment.method);
paymentSpan.setAttribute('payment.transaction_id', payment.transactionId);
} finally {
paymentSpan.end();
}

span.addEvent('checkout completed');
span.end();

구성 예제

다양한 환경에 대한 구성 파일 및 설정 예제입니다.

OpenTelemetry Collector 구성

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:
awsxray:
region: us-east-1
awsemf:
region: us-east-1
namespace: ApplicationSignals

service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [awsxray]
metrics:
receivers: [otlp]
processors: [batch]
exporters: [awsemf]

Docker Compose 설정

version: '3.8'
services:
app:
image: my-app:latest
environment:
- OTEL_TRACES_EXPORTER=otlp
- OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318
- OTEL_SERVICE_NAME=my-service
- OTEL_TRACES_SAMPLER=traceidratio
- OTEL_TRACES_SAMPLER_ARG=0.1
depends_on:
- otel-collector

otel-collector:
image: otel/opentelemetry-collector:latest
command: ["--config=/etc/otel-collector-config.yaml"]
volumes:
- ./otel-config.yaml:/etc/otel-collector-config.yaml
ports:
- "4317:4317" # gRPC
- "4318:4318" # HTTP

Kubernetes 배포

apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
annotations:
instrumentation.opentelemetry.io/inject-java: "true"
spec:
containers:
- name: my-app
image: my-app:latest
env:
- name: OTEL_SERVICE_NAME
value: "my-service"
- name: OTEL_TRACES_EXPORTER
value: "otlp"
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://opentelemetry-collector:4318"