Skip to content

Commit e7a6609

Browse files
authored
Merge pull request #3850 from DataDog/tonycthsu/telemetry_log
Implement telemetry log
2 parents b7b99b2 + 5f65370 commit e7a6609

File tree

9 files changed

+247
-0
lines changed

9 files changed

+247
-0
lines changed

lib/datadog/core/telemetry/component.rb

+6
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,12 @@ def integrations_change!
111111
@worker.enqueue(Event::AppIntegrationsChange.new)
112112
end
113113

114+
def log!(event)
115+
return unless @enabled || forked?
116+
117+
@worker.enqueue(event)
118+
end
119+
114120
# Report configuration changes caused by Remote Configuration.
115121
def client_configuration_change!(changes)
116122
return if !@enabled || forked?

lib/datadog/core/telemetry/event.rb

+29
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,35 @@ def payload
334334
end
335335
end
336336

337+
# Telemetry class for the 'logs' event
338+
class Log < Base
339+
LEVELS = {
340+
error: 'ERROR',
341+
debug: 'DEBUG',
342+
warn: 'WARN',
343+
}.freeze
344+
345+
def type
346+
'logs'
347+
end
348+
349+
def initialize(message:, level:)
350+
super()
351+
@message = message
352+
@level = LEVELS.fetch(level) { |k| raise ArgumentError, "Invalid log level :#{k}" }
353+
end
354+
355+
def payload
356+
{
357+
logs: [{
358+
message: @message,
359+
level: @level,
360+
# More optional fields to be added here...
361+
}]
362+
}
363+
end
364+
end
365+
337366
# Telemetry class for the 'distributions' event
338367
class Distributions < GenerateMetrics
339368
def type

lib/datadog/core/telemetry/logging.rb

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# frozen_string_literal: true
2+
3+
require_relative 'event'
4+
5+
module Datadog
6+
module Core
7+
module Telemetry
8+
# Logging interface for sending telemetry logs.
9+
#
10+
# Reporting internal error so that we can fix them.
11+
# IMPORTANT: Make sure to not log any sensitive information.
12+
module Logging
13+
module_function
14+
15+
def report(exception, level:)
16+
# Annoymous exceptions to be logged as <Class:0x00007f8b1c0b3b40>
17+
message = exception.class.name || exception.class.inspect
18+
19+
event = Event::Log.new(
20+
message: message,
21+
level: level
22+
)
23+
24+
if (telemetry = Datadog.send(:components).telemetry)
25+
telemetry.log!(event)
26+
else
27+
Datadog.logger.debug do
28+
"Attempting to send telemetry log when telemetry component is not ready: #{message}"
29+
end
30+
end
31+
end
32+
end
33+
end
34+
end
35+
end

sig/datadog/core/telemetry/component.rbs

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ module Datadog
2525

2626
def integrations_change!: () -> void
2727

28+
def log!: (Datadog::Core::Telemetry::Event::Log) -> void
29+
2830
def inc: (String namespace, String metric_name, Datadog::Core::Telemetry::Metric::input_value value, ?tags: Datadog::Core::Telemetry::Metric::tags_input, ?common: bool) -> void
2931

3032
def dec: (String namespace, String metric_name, Datadog::Core::Telemetry::Metric::input_value value, ?tags: Datadog::Core::Telemetry::Metric::tags_input, ?common: bool) -> void

sig/datadog/core/telemetry/event.rbs

+11
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,17 @@ module Datadog
6767
def initialize: (String namespace, Enumerable[Datadog::Core::Telemetry::Metric::Base] metric_series) -> void
6868
end
6969

70+
class Log < Base
71+
LEVELS: Hash[Symbol, String]
72+
73+
@message: String
74+
@level: "ERROR" | "DEBUG" | "WARN"
75+
76+
def initialize: (message: String, level: Symbol) -> void
77+
78+
def payload: () -> { logs: [{ message: String, level: String }] }
79+
end
80+
7081
class Distributions < GenerateMetrics
7182
end
7283

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module Datadog
2+
module Core
3+
module Telemetry
4+
module Logging
5+
def report: (Exception exception, level: Symbol) -> void
6+
end
7+
end
8+
end
9+
end

spec/datadog/core/telemetry/component_spec.rb

+49
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,55 @@
219219
end
220220
end
221221

222+
describe '#log!' do
223+
after do
224+
telemetry.stop!
225+
end
226+
227+
describe 'when enabled' do
228+
let(:enabled) { true }
229+
it do
230+
event = instance_double(Datadog::Core::Telemetry::Event::Log)
231+
telemetry.log!(event)
232+
233+
expect(worker).to have_received(:enqueue).with(event)
234+
end
235+
236+
context 'when in fork', skip: !Process.respond_to?(:fork) do
237+
it do
238+
expect_in_fork do
239+
event = instance_double(Datadog::Core::Telemetry::Event::Log)
240+
telemetry.log!(event)
241+
242+
expect(worker).to have_received(:enqueue).with(event)
243+
end
244+
end
245+
end
246+
end
247+
248+
describe 'when disabled' do
249+
let(:enabled) { false }
250+
251+
it do
252+
event = instance_double(Datadog::Core::Telemetry::Event::Log)
253+
telemetry.log!(event)
254+
255+
expect(worker).not_to have_received(:enqueue)
256+
end
257+
258+
context 'when in fork', skip: !Process.respond_to?(:fork) do
259+
it do
260+
expect_in_fork do
261+
event = instance_double(Datadog::Core::Telemetry::Event::Log)
262+
telemetry.log!(event)
263+
264+
expect(worker).not_to have_received(:enqueue)
265+
end
266+
end
267+
end
268+
end
269+
end
270+
222271
context 'metrics support' do
223272
let(:metrics_manager) { spy(:metrics_manager) }
224273
let(:namespace) { double('namespace') }

spec/datadog/core/telemetry/event_spec.rb

+47
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,53 @@ def contain_configuration(*array)
213213
end
214214
end
215215

216+
context 'Logs' do
217+
it do
218+
event = Datadog::Core::Telemetry::Event::Log.new(message: 'Hi', level: :error)
219+
expect(event.type).to eq('logs')
220+
expect(event.payload).to eq(
221+
{
222+
logs: [{
223+
message: 'Hi',
224+
level: 'ERROR'
225+
}]
226+
}
227+
)
228+
end
229+
230+
it do
231+
event = Datadog::Core::Telemetry::Event::Log.new(message: 'Hi', level: :debug)
232+
expect(event.type).to eq('logs')
233+
expect(event.payload).to eq(
234+
{
235+
logs: [{
236+
message: 'Hi',
237+
level: 'DEBUG'
238+
}]
239+
}
240+
)
241+
end
242+
243+
it do
244+
event = Datadog::Core::Telemetry::Event::Log.new(message: 'Hi', level: :warn)
245+
expect(event.type).to eq('logs')
246+
expect(event.payload).to eq(
247+
{
248+
logs: [{
249+
message: 'Hi',
250+
level: 'WARN'
251+
}]
252+
}
253+
)
254+
end
255+
256+
it do
257+
expect do
258+
Datadog::Core::Telemetry::Event::Log.new(message: 'Hi', level: :unknown)
259+
end.to raise_error(ArgumentError, /Invalid log level/)
260+
end
261+
end
262+
216263
context 'GenerateMetrics' do
217264
let(:event) { described_class::GenerateMetrics.new(namespace, metrics) }
218265

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
require 'spec_helper'
2+
3+
require 'datadog/core/telemetry/logging'
4+
require 'datadog/core/telemetry/component'
5+
6+
RSpec.describe Datadog::Core::Telemetry::Logging do
7+
describe '.report' do
8+
context 'with named exception' do
9+
it 'sends a log event to via telemetry' do
10+
telemetry = instance_double(Datadog::Core::Telemetry::Component)
11+
allow(Datadog.send(:components)).to receive(:telemetry).and_return(telemetry)
12+
expect(telemetry).to receive(:log!).with(instance_of(Datadog::Core::Telemetry::Event::Log)) do |event|
13+
expect(event.payload).to include(logs: [{ message: 'RuntimeError', level: 'ERROR' }])
14+
end
15+
16+
begin
17+
raise 'Invalid token: p@ssw0rd'
18+
rescue StandardError => e
19+
described_class.report(e, level: :error)
20+
end
21+
end
22+
end
23+
24+
context 'with anonymous exception' do
25+
it 'sends a log event to via telemetry' do
26+
telemetry = instance_double(Datadog::Core::Telemetry::Component)
27+
allow(Datadog.send(:components)).to receive(:telemetry).and_return(telemetry)
28+
expect(telemetry).to receive(:log!).with(instance_of(Datadog::Core::Telemetry::Event::Log)) do |event|
29+
expect(event.payload).to include(logs: [{ message: /#<Class:/, level: 'ERROR' }])
30+
end
31+
32+
customer_exception = Class.new(StandardError)
33+
34+
begin
35+
raise customer_exception, 'Invalid token: p@ssw0rd'
36+
rescue StandardError => e
37+
described_class.report(e, level: :error)
38+
end
39+
end
40+
end
41+
42+
context 'when telemetry component is not available' do
43+
it 'does not sends a log event to via telemetry' do
44+
logger = Logger.new($stdout)
45+
expect(Datadog.send(:components)).to receive(:telemetry).and_return(nil)
46+
expect(Datadog).to receive(:logger).and_return(logger)
47+
expect(logger).to receive(:debug).with(no_args) do |&block|
48+
expect(block.call).to match(/Attempting to send telemetry log when telemetry component is not ready/)
49+
end
50+
51+
begin
52+
raise 'Invalid token: p@ssw0rd'
53+
rescue StandardError => e
54+
described_class.report(e, level: :error)
55+
end
56+
end
57+
end
58+
end
59+
end

0 commit comments

Comments
 (0)