应用集成 Jaeger SDK

应用集成 Jaeger SDK

多语言 SDK 支持

Jaeger 支持主流编程语言的客户端库:

语言 客户端库 成熟度
Go jaeger-client-go ⭐⭐⭐⭐⭐
Java jaeger-client-java ⭐⭐⭐⭐⭐
Python jaeger-client-python ⭐⭐⭐⭐
Node.js jaeger-client-node ⭐⭐⭐⭐
C++ jaeger-client-cpp ⭐⭐⭐
.NET jaeger-client-csharp ⭐⭐⭐⭐

Go 应用集成

1. 安装依赖

go get github.com/uber/jaeger-client-go
go get github.com/opentracing/opentracing-go

2. 初始化 Tracer

// tracing.go
package tracing

import (
    "io"
    "time"

    "github.com/opentracing/opentracing-go"
    "github.com/uber/jaeger-client-go"
    "github.com/uber/jaeger-client-go/config"
)

// InitTracer 初始化 Jaeger Tracer
func InitTracer(serviceName string) (opentracing.Tracer, io.Closer, error) {
    cfg := &config.Configuration{
        ServiceName: serviceName,
        
        // 采样配置
        Sampler: &config.SamplerConfig{
            Type:  "const",  // 常量采样
            Param: 1,        // 1 = 100% 采样
        },
        
        // Reporter 配置
        Reporter: &config.ReporterConfig{
            LogSpans:            true,
            BufferFlushInterval: 1 * time.Second,
            LocalAgentHostPort:  "jaeger-agent:6831",  // Agent 地址
        },
    }
    
    tracer, closer, err := cfg.NewTracer(
        config.Logger(jaeger.StdLogger),
    )
    if err != nil {
        return nil, nil, err
    }
    
    // 设置为全局 Tracer
    opentracing.SetGlobalTracer(tracer)
    
    return tracer, closer, nil
}

3. HTTP Server 集成

// main.go
package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
    
    "github.com/opentracing/opentracing-go"
    "github.com/opentracing/opentracing-go/ext"
)

func main() {
    // 初始化 Tracer
    tracer, closer, err := InitTracer("my-service")
    if err != nil {
        log.Fatal(err)
    }
    defer closer.Close()
    
    // 注册 Handler
    http.HandleFunc("/api/users", TracingMiddleware(GetUsersHandler))
    
    log.Println("Server starting on :8080")
    http.ListenAndServe(":8080", nil)
}

// TracingMiddleware HTTP 追踪中间件
func TracingMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // 从 HTTP Header 提取 Span Context
        spanCtx, _ := opentracing.GlobalTracer().Extract(
            opentracing.HTTPHeaders,
            opentracing.HTTPHeadersCarrier(r.Header),
        )
        
        // 创建新的 Span
        span := opentracing.GlobalTracer().StartSpan(
            fmt.Sprintf("%s %s", r.Method, r.URL.Path),
            ext.RPCServerOption(spanCtx),
        )
        defer span.Finish()
        
        // 设置标准 Tags
        ext.HTTPMethod.Set(span, r.Method)
        ext.HTTPUrl.Set(span, r.URL.String())
        ext.Component.Set(span, "net/http")
        
        // 将 Span 放入 Context
        ctx := opentracing.ContextWithSpan(r.Context(), span)
        
        // 创建自定义 ResponseWriter 来捕获状态码
        rw := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
        
        // 调用下一个 Handler
        next.ServeHTTP(rw, r.WithContext(ctx))
        
        // 设置响应状态码
        ext.HTTPStatusCode.Set(span, uint16(rw.statusCode))
        if rw.statusCode >= 400 {
            ext.Error.Set(span, true)
        }
    }
}

// responseWriter 包装 http.ResponseWriter 以捕获状态码
type responseWriter struct {
    http.ResponseWriter
    statusCode int
}

func (rw *responseWriter) WriteHeader(code int) {
    rw.statusCode = code
    rw.ResponseWriter.WriteHeader(code)
}

// GetUsersHandler 业务处理函数
func GetUsersHandler(w http.ResponseWriter, r *http.Request) {
    span, ctx := opentracing.StartSpanFromContext(r.Context(), "GetUsers")
    defer span.Finish()
    
    // 设置自定义 Tags
    span.SetTag("user.type", "admin")
    
    // 调用数据库
    users, err := GetUsersFromDB(ctx)
    if err != nil {
        span.SetTag("error", true)
        span.LogKV("event", "error", "message", err.Error())
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    // 记录日志
    span.LogKV("event", "users_fetched", "count", len(users))
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(users)
}

// GetUsersFromDB 数据库查询(带追踪)
func GetUsersFromDB(ctx context.Context) ([]User, error) {
    span, _ := opentracing.StartSpanFromContext(ctx, "db.query")
    defer span.Finish()
    
    // 设置数据库 Tags
    span.SetTag("db.type", "mysql")
    span.SetTag("db.instance", "users_db")
    span.SetTag("db.statement", "SELECT * FROM users")
    
    // 模拟数据库查询
    time.Sleep(50 * time.Millisecond)
    
    return []User{{ID: 1, Name: "Alice"}}, nil
}

4. HTTP Client 集成

// client.go
func CallExternalAPI(ctx context.Context, url string) (*http.Response, error) {
    // 创建子 Span
    span, ctx := opentracing.StartSpanFromContext(ctx, "http.client")
    defer span.Finish()
    
    // 设置 Tags
    ext.SpanKindRPCClient.Set(span)
    ext.HTTPUrl.Set(span, url)
    ext.HTTPMethod.Set(span, "GET")
    
    // 创建 HTTP 请求
    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return nil, err
    }
    
    // 注入 Span Context 到 HTTP Header
    err = opentracing.GlobalTracer().Inject(
        span.Context(),
        opentracing.HTTPHeaders,
        opentracing.HTTPHeadersCarrier(req.Header),
    )
    if err != nil {
        span.LogKV("event", "inject_error", "error", err.Error())
    }
    
    // 发送请求
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        ext.Error.Set(span, true)
        span.LogKV("event", "error", "message", err.Error())
        return nil, err
    }
    
    // 记录响应状态
    ext.HTTPStatusCode.Set(span, uint16(resp.StatusCode))
    if resp.StatusCode >= 400 {
        ext.Error.Set(span, true)
    }
    
    return resp, nil
}

5. gRPC 集成

// grpc_client.go
import (
    "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing"
    "google.golang.org/grpc"
)

func NewGRPCClient(addr string) (*grpc.ClientConn, error) {
    return grpc.Dial(
        addr,
        grpc.WithUnaryInterceptor(
            grpc_opentracing.UnaryClientInterceptor(
                grpc_opentracing.WithTracer(opentracing.GlobalTracer()),
            ),
        ),
        grpc.WithStreamInterceptor(
            grpc_opentracing.StreamClientInterceptor(
                grpc_opentracing.WithTracer(opentracing.GlobalTracer()),
            ),
        ),
    )
}

// grpc_server.go
func NewGRPCServer() *grpc.Server {
    return grpc.NewServer(
        grpc.UnaryInterceptor(
            grpc_opentracing.UnaryServerInterceptor(
                grpc_opentracing.WithTracer(opentracing.GlobalTracer()),
            ),
        ),
        grpc.StreamInterceptor(
            grpc_opentracing.StreamServerInterceptor(
                grpc_opentracing.WithTracer(opentracing.GlobalTracer()),
            ),
        ),
    )
}

Java/Spring Boot 集成

1. 添加依赖

<!-- pom.xml -->
<dependencies>
    <!-- Spring Boot -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- Jaeger Client -->
    <dependency>
        <groupId>io.jaegertracing</groupId>
        <artifactId>jaeger-client</artifactId>
        <version>1.8.1</version>
    </dependency>
    
    <!-- OpenTracing Spring Web -->
    <dependency>
        <groupId>io.opentracing.contrib</groupId>
        <artifactId>opentracing-spring-web-starter</artifactId>
        <version>4.1.0</version>
    </dependency>
</dependencies>

2. 配置 Tracer

// JaegerConfig.java
@Configuration
public class JaegerConfig {
    
    @Bean
    public Tracer jaegerTracer() {
        // 采样配置
        SamplerConfiguration samplerConfig = SamplerConfiguration.fromEnv()
            .withType("const")
            .withParam(1);
        
        // Reporter 配置
        ReporterConfiguration reporterConfig = ReporterConfiguration.fromEnv()
            .withLogSpans(true)
            .withFlushInterval(1000)
            .withMaxQueueSize(10000)
            .withSender(
                SenderConfiguration.fromEnv()
                    .withAgentHost("jaeger-agent")
                    .withAgentPort(6831)
            );
        
        // Tracer 配置
        return Configuration.fromEnv("my-service")
            .withSampler(samplerConfig)
            .withReporter(reporterConfig)
            .getTracer();
    }
}

3. Controller 示例

// UserController.java
@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @Autowired
    private Tracer tracer;
    
    @Autowired
    private UserService userService;
    
    @GetMapping("/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        // 获取当前 Span
        Span span = tracer.activeSpan();
        if (span != null) {
            span.setTag("user.id", id.toString());
            span.log("Fetching user");
        }
        
        try {
            User user = userService.getUserById(id);
            return ResponseEntity.ok(user);
        } catch (Exception e) {
            if (span != null) {
                Tags.ERROR.set(span, true);
                span.log(Map.of(
                    "event", "error",
                    "error.object", e,
                    "message", e.getMessage()
                ));
            }
            throw e;
        }
    }
}

4. Service 层追踪

// UserService.java
@Service
public class UserService {
    
    @Autowired
    private Tracer tracer;
    
    @Autowired
    private UserRepository userRepository;
    
    public User getUserById(Long id) {
        // 创建子 Span
        Span span = tracer.buildSpan("getUserById")
            .asChildOf(tracer.activeSpan())
            .start();
        
        try (Scope scope = tracer.scopeManager().activate(span)) {
            span.setTag("service.method", "getUserById");
            span.setTag("user.id", id.toString());
            
            User user = userRepository.findById(id)
                .orElseThrow(() -> new UserNotFoundException(id));
            
            span.log("User found");
            return user;
        } catch (Exception e) {
            Tags.ERROR.set(span, true);
            span.log(Map.of("event", "error", "message", e.getMessage()));
            throw e;
        } finally {
            span.finish();
        }
    }
}

5. RestTemplate 集成

// RestTemplateConfig.java
@Configuration
public class RestTemplateConfig {
    
    @Bean
    public RestTemplate restTemplate(Tracer tracer) {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setInterceptors(
            Collections.singletonList(
                new TracingClientHttpRequestInterceptor(tracer)
            )
        );
        return restTemplate;
    }
}

Python/Flask 集成

1. 安装依赖

pip install jaeger-client flask-opentracing

2. 初始化 Tracer

# tracing.py
from jaeger_client import Config
from flask_opentracing import FlaskTracing

def init_tracer(service_name='my-service'):
    config = Config(
        config={
            'sampler': {
                'type': 'const',
                'param': 1,
            },
            'logging': True,
            'local_agent': {
                'reporting_host': 'jaeger-agent',
                'reporting_port': 6831,
            },
        },
        service_name=service_name,
        validate=True,
    )
    return config.initialize_tracer()

3. Flask 应用集成

# app.py
from flask import Flask, jsonify, request
from flask_opentracing import FlaskTracing
from opentracing_instrumentation.client_hooks import install_all_patches
import requests

app = Flask(__name__)

# 初始化 Tracer
tracer = init_tracer('flask-service')
tracing = FlaskTracing(tracer, True, app)

# 自动追踪所有 HTTP 请求
install_all_patches()

@app.route('/api/users/<int:user_id>')
def get_user(user_id):
    # 获取当前 Span
    span = tracer.active_span
    if span:
        span.set_tag('user.id', user_id)
        span.log_kv({'event': 'get_user', 'user_id': user_id})
    
    # 调用数据库
    user = get_user_from_db(user_id)
    
    # 调用外部服务
    orders = get_user_orders(user_id)
    
    return jsonify({
        'user': user,
        'orders': orders
    })

def get_user_from_db(user_id):
    # 创建子 Span
    with tracer.start_active_span('db.query') as scope:
        scope.span.set_tag('db.type', 'postgresql')
        scope.span.set_tag('db.statement', 'SELECT * FROM users WHERE id=?')
        
        # 模拟数据库查询
        import time
        time.sleep(0.05)
        
        return {'id': user_id, 'name': 'Alice'}

def get_user_orders(user_id):
    # 创建子 Span
    with tracer.start_active_span('http.client') as scope:
        scope.span.set_tag('http.url', f'http://order-service/api/orders')
        scope.span.set_tag('http.method', 'GET')
        
        # 发送 HTTP 请求(自动传播 Context)
        response = requests.get(
            f'http://order-service/api/orders?user_id={user_id}'
        )
        
        scope.span.set_tag('http.status_code', response.status_code)
        return response.json()

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)

Node.js/Express 集成

1. 安装依赖

npm install jaeger-client express-opentracing opentracing

2. 初始化 Tracer

// tracing.js
const initTracer = require('jaeger-client').initTracer;

function initJaegerTracer(serviceName) {
  const config = {
    serviceName: serviceName,
    sampler: {
      type: 'const',
      param: 1,
    },
    reporter: {
      logSpans: true,
      agentHost: 'jaeger-agent',
      agentPort: 6831,
    },
  };
  
  const options = {
    logger: {
      info: msg => console.log('INFO', msg),
      error: msg => console.log('ERROR', msg),
    },
  };
  
  return initTracer(config, options);
}

module.exports = initJaegerTracer;

3. Express 应用集成

// app.js
const express = require('express');
const initTracer = require('./tracing');
const { middleware } = require('express-opentracing');

const app = express();
const tracer = initTracer('nodejs-service');

// 应用追踪中间件
app.use(middleware({ tracer }));

// API 路由
app.get('/api/users/:id', async (req, res) => {
  const span = req.span;
  span.setTag('user.id', req.params.id);
  span.log({ event: 'get_user', user_id: req.params.id });
  
  try {
    // 调用数据库
    const user = await getUserFromDB(req.params.id, span);
    
    // 调用外部服务
    const orders = await getUserOrders(req.params.id, span);
    
    res.json({ user, orders });
  } catch (error) {
    span.setTag('error', true);
    span.log({ event: 'error', message: error.message });
    res.status(500).json({ error: error.message });
  }
});

async function getUserFromDB(userId, parentSpan) {
  // 创建子 Span
  const span = tracer.startSpan('db.query', { childOf: parentSpan });
  span.setTag('db.type', 'mongodb');
  span.setTag('db.statement', 'db.users.findOne({ _id: userId })');
  
  try {
    // 模拟数据库查询
    await new Promise(resolve => setTimeout(resolve, 50));
    return { id: userId, name: 'Alice' };
  } finally {
    span.finish();
  }
}

async function getUserOrders(userId, parentSpan) {
  const span = tracer.startSpan('http.client', { childOf: parentSpan });
  span.setTag('http.url', 'http://order-service/api/orders');
  span.setTag('http.method', 'GET');
  
  try {
    const axios = require('axios');
    
    // 注入 Trace Context 到 Headers
    const headers = {};
    tracer.inject(span, 'http_headers', headers);
    
    const response = await axios.get(
      `http://order-service/api/orders?user_id=${userId}`,
      { headers }
    );
    
    span.setTag('http.status_code', response.status);
    return response.data;
  } catch (error) {
    span.setTag('error', true);
    span.log({ event: 'error', message: error.message });
    throw error;
  } finally {
    span.finish();
  }
}

app.listen(8080, () => {
  console.log('Server running on port 8080');
});

Kubernetes 配置

环境变量注入

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  template:
    spec:
      containers:
      - name: myapp
        image: myapp:latest
        env:
        # Jaeger Agent 地址
        - name: JAEGER_AGENT_HOST
          value: "jaeger-agent.observability.svc.cluster.local"
        - name: JAEGER_AGENT_PORT
          value: "6831"
        
        # 服务名称
        - name: JAEGER_SERVICE_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.labels['app']
        
        # 采样配置
        - name: JAEGER_SAMPLER_TYPE
          value: "probabilistic"
        - name: JAEGER_SAMPLER_PARAM
          value: "0.1"  # 10% 采样
        
        # 日志配置
        - name: JAEGER_REPORTER_LOG_SPANS
          value: "true"
        - name: JAEGER_REPORTER_MAX_QUEUE_SIZE
          value: "100"

Sidecar 模式

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  template:
    spec:
      containers:
      # 应用容器
      - name: myapp
        image: myapp:latest
        env:
        - name: JAEGER_AGENT_HOST
          value: "localhost"  # Sidecar 在同一 Pod
        - name: JAEGER_AGENT_PORT
          value: "6831"
      
      # Jaeger Agent Sidecar
      - name: jaeger-agent
        image: jaegertracing/jaeger-agent:1.51
        ports:
        - containerPort: 6831
          protocol: UDP
        - containerPort: 6832
          protocol: UDP
        args:
        - "--reporter.grpc.host-port=jaeger-collector.observability:14250"
        resources:
          requests:
            cpu: 50m
            memory: 64Mi
          limits:
            cpu: 100m
            memory: 128Mi

总结

SDK 选择建议

场景 推荐方案
新项目 OpenTelemetry SDK
已有 Jaeger Jaeger Client SDK
需要切换后端 OpenTelemetry
Spring Boot OpenTracing Spring
微服务 Service Mesh (Istio)

集成检查清单

初始化 Tracer

  • 配置服务名称
  • 设置采样策略
  • 配置 Reporter

HTTP 追踪

  • 请求入口埋点
  • 客户端调用埋点
  • Context 传播

数据库追踪

  • SQL 查询埋点
  • 连接池监控

异步任务

  • 消息队列埋点
  • 后台任务追踪

错误处理

  • 异常捕获和记录
  • 错误标记

下一节将介绍分布式追踪的最佳实践和性能优化。