|
17 | 17 | module Mongo
|
18 | 18 | module Tracing
|
19 | 19 | module OpenTelemetry
|
| 20 | + # CommandTracer is responsible for tracing MongoDB server commands using OpenTelemetry. |
| 21 | + # |
| 22 | + # @api private |
20 | 23 | class CommandTracer
|
21 |
| - def initialize(otel_tracer, query_text_max_length: 0) |
| 24 | + extend Forwardable |
| 25 | + |
| 26 | + def_delegators :@parent_tracer, |
| 27 | + :cursor_context_map, |
| 28 | + :parent_context_for, |
| 29 | + :transaction_context_map, |
| 30 | + :transaction_map_key |
| 31 | + |
| 32 | + def initialize(otel_tracer, parent_tracer, query_text_max_length: 0) |
22 | 33 | @otel_tracer = otel_tracer
|
| 34 | + @parent_tracer = parent_tracer |
23 | 35 | @query_text_max_length = query_text_max_length
|
24 | 36 | end
|
25 | 37 |
|
26 |
| - def trace_command(message, _operation_context, connection) |
27 |
| - @otel_tracer.in_span( |
| 38 | + def trace_command(message, operation_context, connection) |
| 39 | + parent_context = parent_context_for(operation_context, cursor_id(message)) |
| 40 | + span = @otel_tracer.start_span( |
28 | 41 | command_span_name(message),
|
29 | 42 | attributes: span_attributes(message, connection),
|
| 43 | + with_parent: parent_context, |
30 | 44 | kind: :client
|
31 |
| - ) do |span, _context| |
| 45 | + ) |
| 46 | + ::OpenTelemetry::Trace.with_span(span) do |s, c| |
| 47 | + # TODO: process cursor context if applicable |
32 | 48 | yield.tap do |result|
|
33 |
| - if result.respond_to?(:cursor_id) && result.cursor_id.positive? |
34 |
| - span.set_attribute('db.mongodb.cursor_id', result.cursor_id) |
35 |
| - end |
| 49 | + process_cursor_context(result, cursor_id(message), c, s) |
36 | 50 | end
|
37 | 51 | end
|
| 52 | + rescue Exception => e |
| 53 | + span&.record_exception(e) |
| 54 | + span&.status = ::OpenTelemetry::Trace::Status.error("Unhandled exception of type: #{e.class}") |
| 55 | + raise e |
| 56 | + ensure |
| 57 | + span&.finish |
38 | 58 | end
|
39 | 59 |
|
40 | 60 | private
|
41 | 61 |
|
42 | 62 | def span_attributes(message, connection)
|
43 | 63 | {
|
44 | 64 | 'db.system' => 'mongodb',
|
45 |
| - 'db.namespace' => message.documents.first['$db'], |
| 65 | + 'db.namespace' => database(message), |
46 | 66 | 'db.collection.name' => collection_name(message),
|
47 |
| - 'db.operation.name' => message.documents.first.keys.first, |
| 67 | + 'db.command.name' => command_name(message), |
48 | 68 | 'server.port' => connection.address.port,
|
49 | 69 | 'server.address' => connection.address.host,
|
50 | 70 | 'network.transport' => connection.transport.to_s,
|
51 | 71 | 'db.mongodb.server_connection_id' => connection.server.description.server_connection_id,
|
52 | 72 | 'db.mongodb.driver_connection_id' => connection.id,
|
| 73 | + 'db.mongodb.cursor_id' => cursor_id(message), |
53 | 74 | 'db.query.text' => query_text(message)
|
54 | 75 | }.compact
|
55 | 76 | end
|
56 | 77 |
|
| 78 | + def process_cursor_context(result, cursor_id, context, span) |
| 79 | + if result.respond_to?(:cursor_id) && result.cursor_id.positive? |
| 80 | + span.set_attribute('db.mongodb.cursor_id', result.cursor_id) |
| 81 | + end |
| 82 | + end |
| 83 | + |
57 | 84 | def command_span_name(message)
|
58 |
| - message.documents.first.keys.first |
| 85 | + if (coll_name = collection_name(message)) |
| 86 | + "#{command_name(message)} #{database(message)}.#{coll_name}" |
| 87 | + else |
| 88 | + "#{command_name(message)} #{database(message)}" |
| 89 | + end |
59 | 90 | end
|
60 | 91 |
|
61 | 92 | def collection_name(message)
|
62 | 93 | case message.documents.first.keys.first
|
63 | 94 | when 'getMore'
|
64 |
| - message.documents.first['collection'] |
| 95 | + message.documents.first['collection'].to_s |
65 | 96 | else
|
66 |
| - message.documents.first.values.first |
| 97 | + message.documents.first.values.first.to_s |
67 | 98 | end
|
68 | 99 | end
|
69 | 100 |
|
| 101 | + def command_name(message) |
| 102 | + message.documents.first.keys.first.to_s |
| 103 | + end |
| 104 | + |
| 105 | + def database(message) |
| 106 | + message.documents.first['$db'].to_s |
| 107 | + end |
| 108 | + |
70 | 109 | def query_text?
|
71 | 110 | @query_text_max_length.positive?
|
72 | 111 | end
|
73 | 112 |
|
| 113 | + def cursor_id(message) |
| 114 | + if command_name(message) == 'getMore' |
| 115 | + message.documents.first['getMore'].value |
| 116 | + end |
| 117 | + end |
| 118 | + |
74 | 119 | EXCLUDED_KEYS = %w[lsid $db $clusterTime signature].freeze
|
75 | 120 | ELLIPSES = '...'
|
76 | 121 |
|
|
0 commit comments