Class: Datadog::Tracing::Contrib::Rack::TraceMiddleware

Inherits:
Object
  • Object
show all
Defined in:
lib/datadog/tracing/contrib/rack/middlewares.rb

Overview

TraceMiddleware ensures that the Rack Request is properly traced from the beginning to the end. The middleware adds the request span in the Rack environment so that it can be retrieved by the underlying application. If request tags are not set by the app, they will be set using information available at the Rack level.

Constant Summary collapse

CLOUDWISE_JS_ENABLED =

✅ 优化:缓存 ENV 配置为常量,避免每次请求读取操作系统环境变量 rubocop:disable CustomCops/EnvUsageCop

ENV.fetch('CLOUDWISE_JS_CONFIG', 'false') == 'true'

Instance Method Summary collapse

Constructor Details

#initialize(app) ⇒ TraceMiddleware

rubocop:enable CustomCops/EnvUsageCop



36
37
38
# File 'lib/datadog/tracing/contrib/rack/middlewares.rb', line 36

def initialize(app)
  @app = app
end

Instance Method Details

#call(env) ⇒ Object



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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/datadog/tracing/contrib/rack/middlewares.rb', line 40

def call(env)
  # Find out if this is rack within rack
  previous_request_span = env[Ext::RACK_ENV_REQUEST_SPAN]

  return @app.call(env) if previous_request_span

  boot = Datadog::Core::Remote::Tie.boot

  # Extract distributed tracing context before creating any spans,
  # so that all spans will be added to the distributed trace.
  if configuration[:distributed_tracing]
    trace_digest = Contrib::HTTP.extract(env)
    Tracing.continue_trace!(trace_digest) if trace_digest
  end

  # 需要在创建 span 之前提取信息
  request_headers = Header::RequestHeaderCollection.new(env)

  # 处理 CLOUDWISEREQUESTINFO(前端调用)
  cloudwise_info = request_headers.get('CLOUDWISEREQUESTINFO')
  request_id = nil

  if cloudwise_info
    # 提取 request_id
    request_id = extract_request_id(cloudwise_info)

    if request_id
      Datadog.logger.debug do
        "Extracted request_id from CLOUDWISEREQUESTINFO: #{request_id}"
      end
    end
  end

  # 处理 CLOUDWISE(外部服务调用,如 Java/PHP)
  cloudwise_header = request_headers.get('CLOUDWISE')

  TraceProxyMiddleware.call(env, configuration) do
    trace_options = {type: Tracing::::Ext::HTTP::TYPE_INBOUND}
    trace_options[:service] = configuration[:service_name] if configuration[:service_name]

    # start a new request span and attach it to the current Rack environment;
    # we must ensure that the span `resource` is set later
    request_span = Tracing.trace(Ext::SPAN_REQUEST, **trace_options)
    request_span.resource = nil

    # 如果存在 request_id,将其作为 span 的 cwsa_trace 字段
    if request_id
      request_span.set_tag('cwsa_trace', request_id)
      Datadog.logger.debug do
        "Set cwsa_trace tag on span: #{request_id}"
      end
    end

    # 如果存在 CLOUDWISE 头(外部服务调用),提取并设置字段到 span
    if cloudwise_header
      require_relative '../cloudwise/propagation'
      Cloudwise::Propagation.extract_and_tag_from_header!(request_span, cloudwise_header)
    end

    # 提取 CLOUDWISE-OTHER 头并添加到根 span
    require_relative '../cloudwise/propagation'
    Cloudwise::Propagation.extract_other_from_request!(request_span, request_headers)

    # When tracing and distributed tracing are both disabled, `.active_trace` will be `nil`,
    # Return a null object to continue operation
    request_trace = Tracing.active_trace || TraceOperation.new
    env[Ext::RACK_ENV_REQUEST_SPAN] = request_span

    Datadog::Core::Remote::Tie::Tracing.tag(boot, request_span)

    # Copy the original env, before the rest of the stack executes.
    # Values may change; we want values before that happens.
    original_env = env.dup

    # call the rest of the stack
    status, headers, response = @app.call(env)

    # 如果存在 cloudwise_info,在响应头中添加 CLOUDWISETRACE
    if cloudwise_info
      headers ||= {}
      headers['CLOUDWISETRACE'] = 'true'
      Datadog.logger.debug do
        "Added CLOUDWISETRACE response header"
      end
    end

    # 如果启用了 CLOUDWISE_JS_CONFIG,在响应头中添加 CLOUDWISE(用于 RUM 追踪)
    # 默认关闭,可通过环境变量 CLOUDWISE_JS_CONFIG=true 开启
    # 使用常量替代每次 ENV 读取
    if CLOUDWISE_JS_ENABLED
      headers ||= {}
      require_relative '../cloudwise/propagation'

      # 构建 CLOUDWISE 响应头值
      # Only add CLOUDWISE header if Cloudwise is active (not suspended)
      service_name = Datadog.configuration.service
      if service_name && request_span
        cloudwise_value = Cloudwise::Propagation.build_cloudwise_value(
          span: request_span,
          trace: request_trace,
          service_name: service_name,
          target_url: nil  # 响应头不需要 target_url
        )

        headers['CLOUDWISE'] = cloudwise_value
        Datadog.logger.debug do
          "Added CLOUDWISE response header for RUM: #{cloudwise_value}"
        end
      end
    end

    [status, headers, response]

    # Here we really want to catch *any* exception, not only StandardError,
    # as we really have no clue of what is in the block,
    # and it is user code which should be executed no matter what.
    # It's not a problem since we re-raise it afterwards so for example a
    # SignalException::Interrupt would still bubble up.
  rescue Exception => e # rubocop:disable Lint/RescueException
    # catch exceptions that may be raised in the middleware chain
    # Note: if a middleware catches an Exception without re raising,
    # the Exception cannot be recorded here.
    request_span&.set_error(e)
    raise e
  ensure
    env[Ext::RACK_ENV_REQUEST_SPAN] = previous_request_span if previous_request_span

    if request_span
      # Rack is a really low level interface and it doesn't provide any
      # advanced functionality like routers. Because of that, we assume that
      # the underlying framework or application has more knowledge about
      # the result for this request; `resource` and `tags` are expected to
      # be set in another level but if they're missing, reasonable defaults
      # are used.
      set_request_tags!(request_trace, request_span, env, status, headers, response, original_env || env)

      # ensure the request_span is finished and the context reset;
      # this assumes that the Rack middleware creates a root span
      request_span.finish
    end
  end
end

#extract_request_id(cloudwise_info) ⇒ Object

从 CLOUDWISEREQUESTINFO 中提取 request_id



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/datadog/tracing/contrib/rack/middlewares.rb', line 184

def extract_request_id(cloudwise_info)
  return nil unless cloudwise_info.is_a?(String)

  # 使用正则表达式提取:第一个 _ 之后,@SDK 之前的内容(不区分大小写)
  match = cloudwise_info.match(/_([^_@]+)@SDK/i)

  if match && match[1]
    request_id = match[1]
    return request_id
  else
    Datadog.logger.debug do
      "Could not extract request_id from CLOUDWISEREQUESTINFO: #{cloudwise_info}"
    end
  end

  nil
rescue => e
  Datadog.logger.error do
    "Error extracting request_id from CLOUDWISEREQUESTINFO: #{e.message}"
  end
  nil
end

#set_request_tags!(trace, request_span, env, status, headers, response, original_env) ⇒ Object

rubocop:disable Metrics/AbcSize rubocop:disable Metrics/CyclomaticComplexity rubocop:disable Metrics/PerceivedComplexity rubocop:disable Metrics/MethodLength



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/datadog/tracing/contrib/rack/middlewares.rb', line 211

def set_request_tags!(trace, request_span, env, status, headers, response, original_env)
  request_header_collection = Header::RequestHeaderCollection.new(env)

  # Since it could be mutated, it would be more accurate to fetch from the original env,
  # e.g. ActionDispatch::ShowExceptions middleware with Rails exceptions_app configuration
  original_request_method = original_env['REQUEST_METHOD']

  # request_headers is subject to filtering and configuration so we
  # get the user agent separately
  user_agent = parse_user_agent_header(request_header_collection)

  # The priority
  # 1. User overrides span.resource
  # 2. Configuration
  # 3. Nested App override trace.resource
  # 4. Fallback with verb + status, eq `GET 200`
  request_span.resource ||=
    if configuration[:middleware_names] && env['RESPONSE_MIDDLEWARE']
      "#{env["RESPONSE_MIDDLEWARE"]}##{original_request_method}"
    elsif trace.resource_override?
      trace.resource
    else
      "#{original_request_method} #{status}".strip
    end

  # Overrides the trace resource if it never been set
  # Otherwise, the getter method would delegate to its root span
  trace.resource = request_span.resource unless trace.resource_override?

  request_span.set_tag(Tracing::::Ext::TAG_COMPONENT, Ext::TAG_COMPONENT)
  request_span.set_tag(Tracing::::Ext::TAG_OPERATION, Ext::TAG_OPERATION_REQUEST)
  request_span.set_tag(Tracing::::Ext::TAG_KIND, Tracing::::Ext::SpanKind::TAG_SERVER)

  set_route_and_endpoint_tags(trace: trace, request_span: request_span, status: status, env: env)

  # Set analytics sample rate
  if Contrib::Analytics.enabled?(configuration[:analytics_enabled])
    Contrib::Analytics.set_sample_rate(request_span, configuration[:analytics_sample_rate])
  end

  # Measure service stats
  Contrib::Analytics.set_measured(request_span)

  if request_span.get_tag(Tracing::::Ext::HTTP::TAG_METHOD).nil?
    request_span.set_tag(Tracing::::Ext::HTTP::TAG_METHOD, original_request_method)
  end

  url = parse_url(env, original_env)

  if request_span.get_tag(Tracing::::Ext::HTTP::TAG_URL).nil?
    options = configuration[:quantize] || {}

    # Quantization::HTTP.url base defaults to :show, but we are transitioning
    options[:base] ||= :exclude

    request_span.set_tag(
      Tracing::::Ext::HTTP::TAG_URL,
      Contrib::Utils::Quantization::HTTP.url(url, options)
    )
  end

  if request_span.get_tag(Tracing::::Ext::HTTP::TAG_BASE_URL).nil?
    options = configuration[:quantize]

    unless options[:base] == :show
      base_url = Contrib::Utils::Quantization::HTTP.base_url(url)

      unless base_url.empty?
        request_span.set_tag(
          Tracing::::Ext::HTTP::TAG_BASE_URL,
          base_url
        )
      end
    end
  end

  if request_span.get_tag(Tracing::::Ext::HTTP::TAG_CLIENT_IP).nil?
    Tracing::ClientIp.set_client_ip_tag(
      request_span,
      headers: request_header_collection,
      remote_ip: env['REMOTE_ADDR']
    )
  end

  if request_span.get_tag(Tracing::::Ext::HTTP::TAG_STATUS_CODE).nil? && status
    request_span.set_tag(Tracing::::Ext::HTTP::TAG_STATUS_CODE, status)
  end

  if request_span.get_tag(Tracing::::Ext::HTTP::TAG_USER_AGENT).nil? && user_agent
    request_span.set_tag(Tracing::::Ext::HTTP::TAG_USER_AGENT, user_agent)
  end

  HeaderTagging.tag_request_headers(request_span, request_header_collection, configuration)
  HeaderTagging.tag_response_headers(request_span, headers, configuration) if headers

  # detect if the status code is a 5xx and flag the request span as an error
  # unless it has been already set by the underlying framework
  if request_span.status.zero? && Datadog.configuration.tracing.http_error_statuses.server.include?(status)
    request_span.status = Tracing::::Ext::Errors::STATUS
  end
end