|
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