appsignal 4.10.1-java → 5.0.0.rc.1-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/Rakefile +87 -0
  4. data/appsignal.gemspec +8 -0
  5. data/build_matrix.yml +14 -1
  6. data/ext/appsignal_extension.c +14 -0
  7. data/lib/appsignal/backends.rb +55 -0
  8. data/lib/appsignal/config.rb +140 -1
  9. data/lib/appsignal/demo.rb +1 -1
  10. data/lib/appsignal/event_formatter/action_view/render_formatter.rb +34 -22
  11. data/lib/appsignal/event_formatter/active_job/perform_formatter.rb +35 -0
  12. data/lib/appsignal/event_formatter/active_record/sql_formatter.rb +19 -0
  13. data/lib/appsignal/event_formatter/elastic_search/search_formatter.rb +27 -0
  14. data/lib/appsignal/event_formatter/recorded_elsewhere.rb +17 -0
  15. data/lib/appsignal/event_formatter/rom/sql_formatter.rb +24 -0
  16. data/lib/appsignal/event_formatter/sequel/sql_formatter.rb +5 -0
  17. data/lib/appsignal/event_formatter/view_component/render_formatter.rb +21 -10
  18. data/lib/appsignal/event_formatter.rb +78 -0
  19. data/lib/appsignal/extension.rb +4 -0
  20. data/lib/appsignal/helpers/instrumentation.rb +236 -20
  21. data/lib/appsignal/helpers/metrics.rb +3 -24
  22. data/lib/appsignal/hooks/action_cable.rb +18 -6
  23. data/lib/appsignal/hooks/active_job.rb +128 -39
  24. data/lib/appsignal/hooks/at_exit.rb +4 -1
  25. data/lib/appsignal/hooks/excon.rb +20 -0
  26. data/lib/appsignal/hooks/faraday.rb +16 -0
  27. data/lib/appsignal/hooks/http.rb +5 -0
  28. data/lib/appsignal/hooks/resque.rb +1 -1
  29. data/lib/appsignal/hooks/sequel.rb +32 -2
  30. data/lib/appsignal/hooks/shoryuken.rb +3 -3
  31. data/lib/appsignal/hooks/sidekiq.rb +1 -1
  32. data/lib/appsignal/integrations/action_cable.rb +5 -2
  33. data/lib/appsignal/integrations/active_support_notifications.rb +59 -14
  34. data/lib/appsignal/integrations/data_mapper.rb +14 -2
  35. data/lib/appsignal/integrations/delayed_job_plugin.rb +45 -5
  36. data/lib/appsignal/integrations/dry_monitor.rb +39 -15
  37. data/lib/appsignal/integrations/excon/appsignal_middleware.rb +21 -0
  38. data/lib/appsignal/integrations/excon.rb +52 -15
  39. data/lib/appsignal/integrations/faraday.rb +47 -12
  40. data/lib/appsignal/integrations/http.rb +43 -1
  41. data/lib/appsignal/integrations/mongo_ruby_driver.rb +73 -4
  42. data/lib/appsignal/integrations/net_http.rb +31 -2
  43. data/lib/appsignal/integrations/puma.rb +4 -1
  44. data/lib/appsignal/integrations/que.rb +224 -37
  45. data/lib/appsignal/integrations/railtie.rb +4 -1
  46. data/lib/appsignal/integrations/rake.rb +9 -3
  47. data/lib/appsignal/integrations/redis.rb +22 -1
  48. data/lib/appsignal/integrations/redis_client.rb +22 -1
  49. data/lib/appsignal/integrations/resque.rb +57 -10
  50. data/lib/appsignal/integrations/shoryuken.rb +140 -12
  51. data/lib/appsignal/integrations/sidekiq.rb +73 -16
  52. data/lib/appsignal/integrations/webmachine.rb +47 -4
  53. data/lib/appsignal/loaders/padrino.rb +2 -1
  54. data/lib/appsignal/logger/extension_backend.rb +24 -0
  55. data/lib/appsignal/logger/opentelemetry_backend.rb +66 -0
  56. data/lib/appsignal/logger.rb +13 -9
  57. data/lib/appsignal/metrics/extension_backend.rb +47 -0
  58. data/lib/appsignal/metrics/opentelemetry_backend.rb +89 -0
  59. data/lib/appsignal/opentelemetry/attributes.rb +31 -0
  60. data/lib/appsignal/opentelemetry/dependencies.rb +35 -0
  61. data/lib/appsignal/opentelemetry/error_type.rb +37 -0
  62. data/lib/appsignal/opentelemetry/http_client_request.rb +83 -0
  63. data/lib/appsignal/opentelemetry/http_method.rb +59 -0
  64. data/lib/appsignal/opentelemetry/http_response.rb +30 -0
  65. data/lib/appsignal/opentelemetry/http_server_request.rb +45 -0
  66. data/lib/appsignal/opentelemetry/messaging.rb +82 -0
  67. data/lib/appsignal/opentelemetry/rendering.rb +29 -0
  68. data/lib/appsignal/opentelemetry/sql_db_system.rb +89 -0
  69. data/lib/appsignal/opentelemetry.rb +337 -0
  70. data/lib/appsignal/rack/abstract_middleware.rb +63 -4
  71. data/lib/appsignal/rack/body_wrapper.rb +18 -5
  72. data/lib/appsignal/rack/event_handler.rb +36 -3
  73. data/lib/appsignal/rack/grape_middleware.rb +1 -0
  74. data/lib/appsignal/rack/hanami_middleware.rb +2 -1
  75. data/lib/appsignal/rack/instrumentation_middleware.rb +1 -0
  76. data/lib/appsignal/rack/rails_instrumentation.rb +1 -0
  77. data/lib/appsignal/rack/sinatra_instrumentation.rb +1 -0
  78. data/lib/appsignal/rack.rb +39 -11
  79. data/lib/appsignal/sample_data.rb +4 -0
  80. data/lib/appsignal/transaction/base_backend.rb +107 -0
  81. data/lib/appsignal/transaction/extension_backend.rb +203 -0
  82. data/lib/appsignal/transaction/opentelemetry_backend.rb +796 -0
  83. data/lib/appsignal/transaction.rb +537 -144
  84. data/lib/appsignal/utils/stdout_and_logger_message.rb +9 -0
  85. data/lib/appsignal/version.rb +1 -1
  86. data/lib/appsignal.rb +9 -0
  87. data/sig/appsignal.rbi +426 -37
  88. data/sig/appsignal.rbs +386 -27
  89. metadata +23 -1
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "appsignal/opentelemetry/attributes"
4
+
5
+ module Appsignal
6
+ module Metrics
7
+ # @!visibility private
8
+ #
9
+ # Routes custom metric helper calls through the OpenTelemetry metrics
10
+ # SDK using the meter provider configured at `Appsignal.start` time when
11
+ # collector mode is active. Mirrors the Python integration's
12
+ # `appsignal/metrics.py`:
13
+ #
14
+ # - `set_gauge` uses a synchronous OTel Gauge.
15
+ # - `increment_counter` uses an UpDownCounter so negative increments
16
+ # work (Counter would reject them).
17
+ # - `add_distribution_value` uses a Histogram.
18
+ #
19
+ # Instruments are created once per name and cached: the OTel SDK logs a
20
+ # "duplicate instrument registration" warning and swaps the instrument
21
+ # if `create_*` is called again for the same name. Tags attach at record
22
+ # time, not at instrument creation time.
23
+ module OpenTelemetryBackend
24
+ MUTEX = Mutex.new
25
+
26
+ class << self
27
+ def set_gauge(name, value, tags)
28
+ instrument(:gauge, name).record(
29
+ value.to_f,
30
+ :attributes => Appsignal::OpenTelemetry::Attributes.format(tags)
31
+ )
32
+ end
33
+
34
+ def increment_counter(name, value, tags)
35
+ instrument(:up_down_counter, name).add(
36
+ value.to_f,
37
+ :attributes => Appsignal::OpenTelemetry::Attributes.format(tags)
38
+ )
39
+ end
40
+
41
+ def add_distribution_value(name, value, tags)
42
+ instrument(:histogram, name).record(
43
+ value.to_f,
44
+ :attributes => Appsignal::OpenTelemetry::Attributes.format(tags)
45
+ )
46
+ end
47
+
48
+ # @!visibility private
49
+ #
50
+ # Test-only. Drops the cached meter and instruments so the next
51
+ # call re-resolves `OpenTelemetry.meter_provider`.
52
+ def reset!
53
+ MUTEX.synchronize do
54
+ @meter = nil
55
+ @gauges = nil
56
+ @counters = nil
57
+ @histograms = nil
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ # Fetch the named instrument, creating and caching it on first use.
64
+ # The lookup-or-create runs under the mutex so two concurrent
65
+ # first-time calls don't both create the instrument (which would
66
+ # make the SDK log a duplicate-registration warning).
67
+ def instrument(kind, name)
68
+ name = name.to_s
69
+ MUTEX.synchronize do
70
+ case kind
71
+ when :gauge
72
+ (@gauges ||= {})[name] ||= meter.create_gauge(name)
73
+ when :up_down_counter
74
+ (@counters ||= {})[name] ||= meter.create_up_down_counter(name)
75
+ when :histogram
76
+ (@histograms ||= {})[name] ||= meter.create_histogram(name)
77
+ end
78
+ end
79
+ end
80
+
81
+ # Only called from `instrument` while the mutex is held, so the plain
82
+ # memoisation needs no extra locking of its own.
83
+ def meter
84
+ @meter ||= ::OpenTelemetry.meter_provider.meter("appsignal-helpers")
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appsignal
4
+ module OpenTelemetry
5
+ # @!visibility private
6
+ #
7
+ # Coerces user-supplied tag hashes into a shape the OpenTelemetry SDK
8
+ # accepts as attribute values: string keys, and values restricted to
9
+ # the primitive types the OTLP spec allows. Anything else falls back
10
+ # to `to_s`. Shared by the metric and log backends so both behave
11
+ # identically.
12
+ module Attributes
13
+ class << self
14
+ def format(attrs)
15
+ attrs.each_with_object({}) do |(key, value), result|
16
+ result[key.to_s] = format_value(value)
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def format_value(value)
23
+ case value
24
+ when String, Integer, Float, TrueClass, FalseClass then value
25
+ else value.to_s
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appsignal
4
+ module OpenTelemetry
5
+ # @!visibility private
6
+ #
7
+ # The OpenTelemetry gems collector mode supports, mapped to their version
8
+ # requirements. Each gem maps to an array of requirement strings so a gem
9
+ # can carry more than one constraint later, even though each holds a single
10
+ # `~>` requirement today. These gems are *not* declared in the gemspec:
11
+ # they are optional and only required when collector mode is active. Apps
12
+ # that opt into collector mode install them into their own bundle (see the
13
+ # collector documentation).
14
+ #
15
+ # Each requirement pins a floor and a ceiling. The floor is the version the
16
+ # minimum supported Ruby (3.1) resolves to in our CI collector matrix. The
17
+ # ceiling is a pessimistic `~>` cap at the next major version. We set the
18
+ # cap loosely on purpose so it does not block customers from updating these
19
+ # gems within a major version.
20
+ #
21
+ # This file must stay free of any other dependency so it can be required
22
+ # directly from a Gemfile (see `gemfiles/collector.rb`) and from the
23
+ # runtime version gate in `Appsignal::OpenTelemetry.configure` without
24
+ # loading the rest of the gem.
25
+ REQUIRED_GEMS = {
26
+ "opentelemetry-sdk" => ["~> 1.10"],
27
+ "opentelemetry-common" => ["~> 0.23"],
28
+ "opentelemetry-metrics-sdk" => ["~> 0.12"],
29
+ "opentelemetry-logs-sdk" => ["~> 0.4"],
30
+ "opentelemetry-exporter-otlp" => ["~> 0.32"],
31
+ "opentelemetry-exporter-otlp-metrics" => ["~> 0.7"],
32
+ "opentelemetry-exporter-otlp-logs" => ["~> 0.3"]
33
+ }.freeze
34
+ end
35
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appsignal
4
+ module OpenTelemetry
5
+ # @!visibility private
6
+ #
7
+ # Builds the OpenTelemetry attribute that describes what kind of failure
8
+ # ended an operation.
9
+ #
10
+ # The semantic conventions ask for `error.type` on a span whose operation
11
+ # failed, and for it to be left unset on one that succeeded. The value has to
12
+ # have low cardinality, which for an exception means its class name rather
13
+ # than its message. A failure we have no name for becomes `_OTHER`, which is
14
+ # the fallback the conventions define.
15
+ module ErrorType
16
+ ATTRIBUTE = "error.type"
17
+
18
+ # The value the semantic conventions use for a failure the
19
+ # instrumentation has no name for.
20
+ OTHER = "_OTHER"
21
+
22
+ class << self
23
+ # The attributes describing the given failure, as a Hash to pass to
24
+ # `add_opentelemetry_attributes`. Takes the name of the failure: an
25
+ # exception's class name, or the error code a datastore reported.
26
+ #
27
+ # An anonymous exception class has no name, and a datastore does not
28
+ # always report a code, so a missing name falls back to `_OTHER` rather
29
+ # than leaving the span with no `error.type` at all.
30
+ def attributes_for(name)
31
+ value = name.to_s
32
+ { ATTRIBUTE => value.empty? ? OTHER : value }
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appsignal
4
+ module OpenTelemetry
5
+ # @!visibility private
6
+ #
7
+ # Builds the OpenTelemetry attributes that describe an outgoing HTTP request.
8
+ #
9
+ # The semantic conventions ask for the request method, the host and port
10
+ # being called, and the full URL. Those together are also what the trace
11
+ # timeline reads to recognize an outgoing request.
12
+ #
13
+ # The URL is built from the parts rather than taken whole, which has two
14
+ # consequences worth knowing about.
15
+ #
16
+ # It never contains credentials. A URL can carry a username and password,
17
+ # which the conventions say must not be sent. Building the URL from the
18
+ # scheme, host, port and path means there is nowhere for them to come from.
19
+ #
20
+ # It never contains the query string either. The conventions do ask for it,
21
+ # but the query string of a call to somebody else's API is the part most
22
+ # likely to carry a key or a token, and unlike an incoming request it is not
23
+ # something the application chose the shape of. Anything a caller passes as a
24
+ # path is cut at the first question mark for the same reason, because some
25
+ # clients hand us a request target rather than a path.
26
+ module HttpClientRequest
27
+ ADDRESS_ATTRIBUTE = "server.address"
28
+ PORT_ATTRIBUTE = "server.port"
29
+ URL_ATTRIBUTE = "url.full"
30
+
31
+ # The port each scheme uses when a URL does not name one. Left out of the
32
+ # URL, which is how a URL is normally written, but still reported as the
33
+ # port, which the conventions ask for either way.
34
+ DEFAULT_PORTS = {
35
+ "http" => 80,
36
+ "https" => 443
37
+ }.freeze
38
+
39
+ class << self
40
+ # The attributes describing the given request, as a Hash to pass to
41
+ # `add_opentelemetry_attributes`. Takes the request's parts, because
42
+ # some clients hand us a URI and others only the parts.
43
+ def attributes_for(method:, scheme: nil, host: nil, port: nil, path: nil)
44
+ attributes = HttpMethod.attributes_for(method)
45
+ attributes[ADDRESS_ATTRIBUTE] = host.to_s unless host.to_s.empty?
46
+
47
+ port = port_for(scheme, port)
48
+ attributes[PORT_ATTRIBUTE] = port if port
49
+
50
+ url = url_for(scheme, host, port, path)
51
+ attributes[URL_ATTRIBUTE] = url if url
52
+
53
+ attributes
54
+ end
55
+
56
+ private
57
+
58
+ # A client that was not given a port uses the one its scheme implies, so
59
+ # report that rather than nothing.
60
+ def port_for(scheme, port)
61
+ Integer(port, :exception => false) || DEFAULT_PORTS[scheme.to_s]
62
+ end
63
+
64
+ def url_for(scheme, host, port, path)
65
+ return if scheme.to_s.empty? || host.to_s.empty?
66
+
67
+ url = +"#{scheme}://#{host}"
68
+ url << ":#{port}" if port && port != DEFAULT_PORTS[scheme.to_s]
69
+ url << path_without_query(path)
70
+ url
71
+ end
72
+
73
+ # Some clients report the path of a request to the root of a host as
74
+ # empty, and others as "/". The request goes to "/" either way, so say so
75
+ # rather than let the URL differ by which client made the request.
76
+ def path_without_query(path)
77
+ without_query = path.to_s.split("?", 2).first.to_s
78
+ without_query.empty? ? "/" : without_query
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appsignal
4
+ module OpenTelemetry
5
+ # @!visibility private
6
+ #
7
+ # Builds the OpenTelemetry attributes that describe an HTTP request method.
8
+ #
9
+ # The semantic conventions treat `http.request.method` as a closed set of
10
+ # known method names, so an arbitrary value cannot be passed through. A
11
+ # method outside the set becomes `_OTHER`, and a method that only matches
12
+ # after upcasing is replaced by its canonical form. Both of those cases keep
13
+ # the value we were given in `http.request.method_original`, so the original
14
+ # is never lost.
15
+ module HttpMethod
16
+ # The methods the semantic conventions know about: those defined in
17
+ # RFC 9110, plus PATCH from RFC 5789.
18
+ KNOWN_METHODS = [
19
+ "CONNECT",
20
+ "DELETE",
21
+ "GET",
22
+ "HEAD",
23
+ "OPTIONS",
24
+ "PATCH",
25
+ "POST",
26
+ "PUT",
27
+ "TRACE"
28
+ ].freeze
29
+
30
+ # The value the semantic conventions use for a method they do not know.
31
+ OTHER = "_OTHER"
32
+
33
+ METHOD_ATTRIBUTE = "http.request.method"
34
+ ORIGINAL_ATTRIBUTE = "http.request.method_original"
35
+
36
+ class << self
37
+ # The attributes describing the given request method, as a Hash to pass
38
+ # to `add_opentelemetry_attributes`. Returns an empty Hash when there is
39
+ # no method to describe, so a caller that could not read one can pass
40
+ # the result on without checking.
41
+ def attributes_for(method)
42
+ original = method.to_s
43
+ return {} if original.empty?
44
+
45
+ # Method names are case sensitive, so a value that already matches a
46
+ # known method is used as it is, with nothing to preserve.
47
+ return { METHOD_ATTRIBUTE => original } if KNOWN_METHODS.include?(original)
48
+
49
+ canonical = original.upcase
50
+ if KNOWN_METHODS.include?(canonical)
51
+ { METHOD_ATTRIBUTE => canonical, ORIGINAL_ATTRIBUTE => original }
52
+ else
53
+ { METHOD_ATTRIBUTE => OTHER, ORIGINAL_ATTRIBUTE => original }
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appsignal
4
+ module OpenTelemetry
5
+ # @!visibility private
6
+ #
7
+ # Builds the OpenTelemetry attributes that describe an HTTP response.
8
+ #
9
+ # The semantic conventions ask for the response status code on the span of an
10
+ # HTTP request, whether that is a request this application handled or one it
11
+ # made. They ask for it only when a response was actually received or sent,
12
+ # so a request that never got one is described without it.
13
+ module HttpResponse
14
+ STATUS_CODE_ATTRIBUTE = "http.response.status_code"
15
+
16
+ class << self
17
+ # The attributes describing the given response status, as a Hash to pass
18
+ # to `add_opentelemetry_attributes`. Returns an empty Hash when there is
19
+ # no status to describe, so a caller whose request produced no response
20
+ # can pass the result on without checking.
21
+ def attributes_for(status)
22
+ code = Integer(status, :exception => false)
23
+ return {} unless code
24
+
25
+ { STATUS_CODE_ATTRIBUTE => code }
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appsignal
4
+ module OpenTelemetry
5
+ # @!visibility private
6
+ #
7
+ # Builds the OpenTelemetry attributes that describe an incoming HTTP
8
+ # request.
9
+ #
10
+ # The semantic conventions ask for the request method, the path and the
11
+ # scheme on the span of a request a server handled. Those three together are
12
+ # also what the trace timeline reads to recognize a web request.
13
+ #
14
+ # The path is the concrete path the request was made to, such as `/users/1`.
15
+ # It is not the route template the application matched it against, which the
16
+ # conventions call `http.route` and which a Rack application does not
17
+ # necessarily have.
18
+ #
19
+ # The query string is asked for whenever the request had one. It is sent
20
+ # whole, without the leading question mark, and it is not filtered here. The
21
+ # collector filters it with the `filter_request_query_parameters` option, and
22
+ # builds the request's query parameters out of it.
23
+ #
24
+ # Every value is optional, because reading any of them from the request can
25
+ # fail. An attribute we have no value for is left out rather than sent
26
+ # empty.
27
+ module HttpServerRequest
28
+ PATH_ATTRIBUTE = "url.path"
29
+ SCHEME_ATTRIBUTE = "url.scheme"
30
+ QUERY_ATTRIBUTE = "url.query"
31
+
32
+ class << self
33
+ # The attributes describing the given request, as a Hash to pass to
34
+ # `add_opentelemetry_attributes`.
35
+ def attributes_for(method:, path: nil, scheme: nil, query: nil)
36
+ attributes = HttpMethod.attributes_for(method)
37
+ attributes[PATH_ATTRIBUTE] = path.to_s unless path.to_s.empty?
38
+ attributes[SCHEME_ATTRIBUTE] = scheme.to_s unless scheme.to_s.empty?
39
+ attributes[QUERY_ATTRIBUTE] = query.to_s unless query.to_s.empty?
40
+ attributes
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appsignal
4
+ module OpenTelemetry
5
+ # @!visibility private
6
+ #
7
+ # Builds the OpenTelemetry attributes that describe a messaging operation,
8
+ # which for AppSignal means a background job being enqueued or performed.
9
+ #
10
+ # The semantic conventions ask for the messaging system, the name of the
11
+ # operation, and the kind of operation it was. The name is the job library's
12
+ # own word for what happened. The kind is one of the five values the
13
+ # conventions define, and AppSignal only ever produces two of them:
14
+ # enqueuing a job sends a message, and performing one processes it.
15
+ #
16
+ # They also ask for the destination of the message, which for a job library
17
+ # is the queue it is on, and for how many messages a span covers when it
18
+ # covers a batch of them.
19
+ #
20
+ # The system is the only part that differs per job library, so each
21
+ # integration passes its own.
22
+ module Messaging
23
+ SYSTEM_ATTRIBUTE = "messaging.system"
24
+ OPERATION_NAME_ATTRIBUTE = "messaging.operation.name"
25
+ OPERATION_TYPE_ATTRIBUTE = "messaging.operation.type"
26
+ DESTINATION_ATTRIBUTE = "messaging.destination.name"
27
+ BATCH_COUNT_ATTRIBUTE = "messaging.batch.message_count"
28
+
29
+ # The operations AppSignal records, and the kind of operation the
30
+ # conventions class each of them as.
31
+ ENQUEUE = "enqueue"
32
+ PERFORM = "perform"
33
+ OPERATION_TYPES = {
34
+ ENQUEUE => "send",
35
+ PERFORM => "process"
36
+ }.freeze
37
+
38
+ class << self
39
+ # The attributes describing a job being enqueued, as a Hash to pass to
40
+ # `add_opentelemetry_attributes`.
41
+ def enqueue_attributes(system, destination: nil, batch_size: nil)
42
+ attributes_for(system, ENQUEUE, destination, batch_size)
43
+ end
44
+
45
+ # The attributes describing a job being performed, as a Hash to pass to
46
+ # `add_opentelemetry_attributes`.
47
+ def perform_attributes(system, destination: nil, batch_size: nil)
48
+ attributes_for(system, PERFORM, destination, batch_size)
49
+ end
50
+
51
+ private
52
+
53
+ def attributes_for(system, operation, destination, batch_size)
54
+ {
55
+ SYSTEM_ATTRIBUTE => system,
56
+ OPERATION_NAME_ATTRIBUTE => operation,
57
+ OPERATION_TYPE_ATTRIBUTE => OPERATION_TYPES.fetch(operation),
58
+ DESTINATION_ATTRIBUTE => destination_name(destination),
59
+ BATCH_COUNT_ATTRIBUTE => batch_count(batch_size)
60
+ }.compact
61
+ end
62
+
63
+ # The queue the job is on, which the conventions call the destination of
64
+ # the message. A job whose queue we cannot name is described without it.
65
+ def destination_name(destination)
66
+ name = destination.to_s
67
+ name unless name.empty?
68
+ end
69
+
70
+ # How many messages the span covers. The conventions ask for this only
71
+ # when a span describes a batch, and say it must not be set on a span
72
+ # that describes a single message.
73
+ def batch_count(batch_size)
74
+ return unless batch_size
75
+
76
+ count = batch_size.to_i
77
+ count if count.positive?
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appsignal
4
+ module OpenTelemetry
5
+ # @!visibility private
6
+ #
7
+ # Builds the OpenTelemetry attributes that describe an event as template
8
+ # rendering.
9
+ #
10
+ # Rendering a template has no semantic convention to describe it, so these
11
+ # events say which group they belong to directly. The trace timeline reads
12
+ # `appsignal.group` before it looks at any convention attribute, and
13
+ # "render" is the group it shows as "Templating".
14
+ module Rendering
15
+ GROUP_ATTRIBUTE = "appsignal.group"
16
+ GROUP = "render"
17
+
18
+ ATTRIBUTES = { GROUP_ATTRIBUTE => GROUP }.freeze
19
+
20
+ class << self
21
+ # The attributes describing an event as template rendering, as a Hash
22
+ # to pass to `add_opentelemetry_attributes`.
23
+ def attributes
24
+ ATTRIBUTES
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appsignal
4
+ module OpenTelemetry
5
+ # @!visibility private
6
+ #
7
+ # Maps a SQL library's own name for the engine it is talking to onto the
8
+ # `db.system.name` semantic conventions value for that engine.
9
+ #
10
+ # Each library names engines differently, and none of them matches the
11
+ # semantic conventions spelling, so every SQL integration needs a lookup.
12
+ # Kept as one map per library, each keyed on that library's own
13
+ # vocabulary, rather than one shared map: ActiveRecord's `ADAPTER_NAME`
14
+ # strings, Sequel's `database_type` symbols, and DataMapper's
15
+ # `DataObjects` connection class names do not collide today, but nothing
16
+ # about that is enforced by combining them. A wrong value passed into the
17
+ # wrong library's lookup would silently return another library's answer
18
+ # instead of nil, and a library's own gaps would be invisible next to two
19
+ # other libraries' complete entries. ROM is the exception: its payload
20
+ # already carries Sequel's own `database_type` symbol, so it is
21
+ # legitimate for it to share Sequel's map, not just convenient.
22
+ #
23
+ # A name none of these maps recognise returns `nil`, so the caller's own
24
+ # `other_sql` fallback applies -- exactly what already happens for a
25
+ # library this gem has no mapping for at all.
26
+ #
27
+ # This intentionally follows the older semantic conventions value set:
28
+ # SQL Server maps to `mssql`, not the current registry's
29
+ # `microsoft.sql_server`, and Oracle maps to `oracle`, not `oracle.db`.
30
+ # That is what the AppSignal collector's sanitizer recognizes today;
31
+ # emitting the newer values would silently turn sanitization off for
32
+ # those engines' queries.
33
+ module SqlDbSystem
34
+ # ActiveRecord's `ADAPTER_NAME`. Rails bundles the Postgres, Mysql2,
35
+ # SQLite and (7.1+) Trilogy adapters; SQL Server and Oracle come from
36
+ # the separate `activerecord-sqlserver-adapter` and
37
+ # `activerecord-oracle_enhanced-adapter` gems, which declare
38
+ # `ADAPTER_NAME` the same way.
39
+ ACTIVE_RECORD = {
40
+ "PostgreSQL" => "postgresql",
41
+ "Mysql2" => "mysql",
42
+ "Trilogy" => "mysql",
43
+ "SQLite" => "sqlite",
44
+ "SQLServer" => "mssql",
45
+ "OracleEnhanced" => "oracle"
46
+ }.freeze
47
+
48
+ # Sequel's `database_type`. ROM's dry-monitor payload reuses this
49
+ # symbol directly, so `name_for_sequel` is also ROM's lookup.
50
+ SEQUEL = {
51
+ :postgres => "postgresql",
52
+ :mysql => "mysql",
53
+ :sqlite => "sqlite",
54
+ :mssql => "mssql",
55
+ :oracle => "oracle"
56
+ }.freeze
57
+
58
+ # DataMapper's `DataObjects` connection classes.
59
+ DATA_MAPPER = {
60
+ "DataObjects::Postgres::Connection" => "postgresql",
61
+ "DataObjects::Mysql::Connection" => "mysql",
62
+ "DataObjects::Sqlite3::Connection" => "sqlite",
63
+ "DataObjects::SqlServer::Connection" => "mssql"
64
+ }.freeze
65
+
66
+ class << self
67
+ # The `db.system.name` value for an ActiveRecord connection's
68
+ # `adapter_name`, or `nil` when the adapter is not one this map
69
+ # recognises.
70
+ def name_for_active_record(adapter_name)
71
+ ACTIVE_RECORD[adapter_name]
72
+ end
73
+
74
+ # The `db.system.name` value for Sequel's `database_type`, or `nil`
75
+ # when it is not one this map recognises. Also the lookup ROM's
76
+ # formatter uses, since ROM reports this same symbol.
77
+ def name_for_sequel(database_type)
78
+ SEQUEL[database_type]
79
+ end
80
+
81
+ # The `db.system.name` value for DataMapper's connection class name,
82
+ # or `nil` when it is not one this map recognises.
83
+ def name_for_data_mapper(connection_class_name)
84
+ DATA_MAPPER[connection_class_name]
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end