Skip to content

Commit

Permalink
Implement telemetry log
Browse files Browse the repository at this point in the history
  • Loading branch information
TonyCTHsu committed Aug 20, 2024
1 parent b7b99b2 commit acbab8c
Show file tree
Hide file tree
Showing 9 changed files with 232 additions and 0 deletions.
6 changes: 6 additions & 0 deletions lib/datadog/core/telemetry/component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ def integrations_change!
@worker.enqueue(Event::AppIntegrationsChange.new)
end

def log!(event)
return unless @enabled || forked?

@worker.enqueue(event)
end

# Report configuration changes caused by Remote Configuration.
def client_configuration_change!(changes)
return if !@enabled || forked?
Expand Down
27 changes: 27 additions & 0 deletions lib/datadog/core/telemetry/event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,33 @@ def payload
end
end

# Telemetry class for the 'logs' event
class Log < Base
LEVELS = {
error: 'ERROR',
debug: 'DEBUG',
warn: 'WARN',
}.freeze

def type
'logs'
end

def initialize(message:, level:)
super()
@message = message
@level = LEVELS.fetch(level) { |k| raise ArgumentError, "Invalid log level :#{k}"}
end

def payload
{
message: @message,
level: @level,
# More optional fields to be added here...
}
end
end

# Telemetry class for the 'distributions' event
class Distributions < GenerateMetrics
def type
Expand Down
35 changes: 35 additions & 0 deletions lib/datadog/core/telemetry/logging.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

require_relative 'event'

module Datadog
module Core
module Telemetry
# Logging interface for sending telemetry logs.
#
# Reporting internal error so that we can fix them.
# IMPORTANT: Make sure to not log any sensitive information.
module Logging
module_function

def report(exception, level:)
# Annoymous exceptions to be logged as <Class:0x00007f8b1c0b3b40>
message = exception.class.name || exception.class.inspect

event = Event::Log.new(
message: message,
level: level
)

if (telemetry = Datadog.send(:components).telemetry)
telemetry.log!(event)
else
Datadog.logger.debug do
"Attempting to send telemetry log when telemetry component is not ready: #{message}"
end
end
end
end
end
end
end
2 changes: 2 additions & 0 deletions sig/datadog/core/telemetry/component.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ module Datadog

def integrations_change!: () -> void

def log!: (Datadog::Core::Telemetry::Event::Log) -> void

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

def dec: (String namespace, String metric_name, Datadog::Core::Telemetry::Metric::input_value value, ?tags: Datadog::Core::Telemetry::Metric::tags_input, ?common: bool) -> void
Expand Down
11 changes: 11 additions & 0 deletions sig/datadog/core/telemetry/event.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,17 @@ module Datadog
def initialize: (String namespace, Enumerable[Datadog::Core::Telemetry::Metric::Base] metric_series) -> void
end

class Log < Base
LEVELS: Hash[Symbol, String]

@message: String
@level: "ERROR" | "DEBUG" | "WARN"

def initialize: (message: String, level: Symbol) -> void

def payload: () -> { message: String, level: String }
end

class Distributions < GenerateMetrics
end

Expand Down
9 changes: 9 additions & 0 deletions sig/datadog/core/telemetry/logging.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module Datadog
module Core
module Telemetry
module Logging
def report: (Exception exception, level: Symbol) -> void
end
end
end
end
49 changes: 49 additions & 0 deletions spec/datadog/core/telemetry/component_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,55 @@
end
end

describe '#log!' do
after do
telemetry.stop!
end

describe 'when enabled' do
let(:enabled) { true }
it do
event = double('event')
telemetry.log!(event)

expect(worker).to have_received(:enqueue).with(event)
end

context 'when in fork', skip: !Process.respond_to?(:fork) do
it do
expect_in_fork do
event = double('event')
telemetry.log!(event)

expect(worker).to have_received(:enqueue).with(event)
end
end
end
end

describe 'when disabled' do
let(:enabled) { false }

it do
event = double('event')
telemetry.log!(event)

expect(worker).not_to have_received(:enqueue)
end

context 'when in fork', skip: !Process.respond_to?(:fork) do
it do
expect_in_fork do
event = double('event')
telemetry.log!(event)

expect(worker).not_to have_received(:enqueue)
end
end
end
end
end

context 'metrics support' do
let(:metrics_manager) { spy(:metrics_manager) }
let(:namespace) { double('namespace') }
Expand Down
35 changes: 35 additions & 0 deletions spec/datadog/core/telemetry/event_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,41 @@ def contain_configuration(*array)
end
end

context 'Logs' do
it do
event = Datadog::Core::Telemetry::Event::Log.new(message: 'Hi', level: :error)
expect(event.type).to eq('logs')
expect(event.payload).to eq(
message: 'Hi',
level: 'ERROR'
)
end

it do
event = Datadog::Core::Telemetry::Event::Log.new(message: 'Hi', level: :debug)
expect(event.type).to eq('logs')
expect(event.payload).to eq(
message: 'Hi',
level: 'DEBUG'
)
end

it do
event = Datadog::Core::Telemetry::Event::Log.new(message: 'Hi', level: :warn)
expect(event.type).to eq('logs')
expect(event.payload).to eq(
message: 'Hi',
level: 'WARN'
)
end

it do
expect do
Datadog::Core::Telemetry::Event::Log.new(message: 'Hi', level: :unknown)
end.to raise_error(ArgumentError, /Invalid log level/)
end
end

context 'GenerateMetrics' do
let(:event) { described_class::GenerateMetrics.new(namespace, metrics) }

Expand Down
58 changes: 58 additions & 0 deletions spec/datadog/core/telemetry/logging_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
require 'spec_helper'

require 'datadog/core/telemetry/logging'

RSpec.describe Datadog::Core::Telemetry::Logging do
describe '.report' do
context 'with named exception' do
it 'sends a log event to via telemetry' do
telemetry = double('telemetry')
allow(Datadog.send(:components)).to receive(:telemetry).and_return(telemetry)
expect(telemetry).to receive(:log!).with(instance_of(Datadog::Core::Telemetry::Event::Log)) do |event|
expect(event.payload).to include(message: 'RuntimeError', level: 'ERROR')
end

begin
raise 'Invalid token: p@ssw0rd'
rescue StandardError => e
described_class.report(e, level: :error)
end
end
end

context 'with anonymous exception' do
it 'sends a log event to via telemetry' do
telemetry = double('telemetry')
allow(Datadog.send(:components)).to receive(:telemetry).and_return(telemetry)
expect(telemetry).to receive(:log!).with(instance_of(Datadog::Core::Telemetry::Event::Log)) do |event|
expect(event.payload).to include(message: /#<Class:/, level: 'ERROR')
end

customer_exception = Class.new(StandardError)

begin
raise customer_exception, 'Invalid token: p@ssw0rd'
rescue StandardError => e
described_class.report(e, level: :error)
end
end
end

context 'when telemetry component is not available' do
it 'does not sends a log event to via telemetry' do
logger = Logger.new($stdout)
expect(Datadog.send(:components)).to receive(:telemetry).and_return(nil)
expect(Datadog).to receive(:logger).and_return(logger)
expect(logger).to receive(:debug).with(no_args) do |&block|
expect(block.call).to match /Attempting to send telemetry log when telemetry component is not ready/
end

begin
raise 'Invalid token: p@ssw0rd'
rescue StandardError => e
described_class.report(e, level: :error)
end
end
end
end
end

0 comments on commit acbab8c

Please sign in to comment.