Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add log devices and write to all of them #1

Closed
wants to merge 123 commits into from

Conversation

mahmoodkhan
Copy link
Collaborator

This PR adds the ability to write to more than one log device at the same time. It also adds a datadog log device that sends logs to the datadog service using its HTTP endpoints.

In the next PR, I will change sending logs to datadog non-blocking by spawning a new thread.

ged added 30 commits May 5, 2012 21:46
I've been trying to do everything in 1.9 first, but it
turns out there's still some 1.8 stuff that I need to support
that I'd like to use this with.
Thanks to Mahlon for spotting this and helping to track it down.
target_regex = /^([\sa-z]\w*)(?:\[(.*)\])?/

target_subclass = t[ target_regex, 1 ]&.strip().to_sym
target_subclass_args = t[ target_regex, 2 ]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we nil-check the target_subclass_args? Again, as an example, as written something like data-dog[foo_key] will return a target_subclass of data and a target_subclass_args of nil.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not all log devices need target_subclass_args e.g. the Appending log device class does not require an argument.

### In the above example:
### "datadog" is the log device to send logs to
### "[data_dog_api_key]" is the argument that will be passed onto the datadog subclass
### "(color)" is the format
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how the format in this example gets used?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't it's just there to show what a config entry looks like. I will remove it.

### datadog[data_dog_api_key] (color)
### In the above example:
### "datadog" is the log device to send logs to
### "[data_dog_api_key]" is the argument that will be passed onto the datadog subclass
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a little unclear to me - what do you think about these changes?
"data_dog_api_key" is the argument that will be passed onto the datadog log device's constructor

(Hash part of the string isn't actually provided, and making it clearer what it's passed into)


require 'loggability/log_device'

# This is the a generalized class that allows its subclasses send
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing a "to" - "subclasses send to". Also, maybe some specific mention of running on a separate thread?

})

## send the batch of logs if it is approaching datadog's payload size limits
if self.logs_queue.size == MAX_BATCH_SIZE || self.batched_messages_bytesize > MAX_BATCH_BYTESIZE
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should check >= MAX_BATCH_SIZE rather than exact equality, just to be resilient to usages where multiple pushs happen before this check

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and may as well make the MAX_BATCH_BYTESIZE check a >= for consistency, too

attr_reader :batched_messages_bytesize


### Sets up the data for the log message and sends it to datadog
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe something like "and sends it to datadog if the queue is full"?

###
### sends all a batch of up to the batched log messages asynchronously to datadog
def send_logs
return if self.logs_queue.size == 0
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.empty?

return if self.logs_queue.size == 0

batched_messages = Concurrent::Array.new
while self.logs_queue.size > 0
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a nitpicky suggestion for readability, but I like using the empty? semantics where it's available, so just throwing this out there:
batched_messages.push( self.logs_queue.pop ) until self.logs_queue.empty?

def send_logs
return if self.logs_queue.size == 0

batched_messages = Concurrent::Array.new
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this array to be thread-safe, right? It's going to be allocated, filled and read all within a single thread with a scope local to this method. Sometimes I get my wired crossed with this stuff though.



### Queue is a thread-safe and it is used to batch log messages
def setup_logs_queue
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just inline this into the constructor? Looks like it's only called there.

@@ -176,7 +223,7 @@
expect( logger.logdev ).to be( original_logdev )
end

it "doesn't re-wrap an AppendingLogDevice" do
it "doesn't re-wrap an Appending" do
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe "an Appending log device"?

with( datadog_logdev.target ).
and_return( Net::HTTPOK )

500.times do
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than hard-code this number again, we should use MAX_BATCH_SIZE

expect( datadog_logdev.http ).not_to receive( :request ).
with( datadog_logdev.target )

499.times do
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, MAX_BATCH_SIZE - 1 please

expect( logger.logdev ).to be_a( Logger::LogDevice )
end

it "can be told to log to datadog and not send messages" do
Copy link

@khiner khiner Nov 26, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This spec description to me reads like you can configure the datadog logger to not send messages, but what's being tested is that it doesn't post until hitting the batch size. Would you mind adding something about the batch size to these two descriptions?

@@ -7,7 +7,7 @@
# Some helper functions for testing. Usage:
#
# # in spec_helpers.rb
# RSpec.configure do |c|
# igure do |c|
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, not sure if we should just delete this whole comment, but some accidentally deleted text

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooops

spec/helpers.rb Outdated
@@ -112,5 +111,11 @@ def all_be_a( expected_class )
c.include( Loggability::SpecHelpers )
c.filter_run_excluding( :configurability ) unless defined?( Configurability )

# c.before(:each) do
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delete?

end


it "verify that the http client is setup correctly" do
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we want to test for specific constant values. IMO, Configurations and constants should be mutable, and the tests should not make assumptions about what the values are. Having this tests just means we have to modify constants in two places instead of one.

Copy link

@khiner khiner Nov 26, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to elaborate, I think the tests below that e.g. the configured batch size is respected when posting to datadog, is valuable, but testing the exact batch size value isn't.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put these specs in there to prevent someone from changing these values to above the datadog's payload limits and not realizing it. But I understand your point. I will just put a note on those constants.

end


it "can prase a config entry and return a Loggability::LogDevice::File log instance" do
Copy link

@khiner khiner Nov 26, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo here and below "prase" 🙏

@mahmoodkhan mahmoodkhan force-pushed the log-to-multiple-devices branch 2 times, most recently from f995446 to 92fcf54 Compare November 26, 2019 22:53
@@ -28,7 +28,6 @@
require 'loggability'
require 'loggability/spechelpers'


Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like some extra spacing changes made it in

Copy link

@khiner khiner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 from me with those changes, nice work!

@mahmoodkhan mahmoodkhan force-pushed the log-to-multiple-devices branch 2 times, most recently from 0a4338d to a93802b Compare November 27, 2019 00:06
@ged
Copy link
Owner

ged commented Jan 8, 2020

I imported this into the canonical repo, and will finish it up in the next few days

@ged
Copy link
Owner

ged commented Jan 8, 2020

Also of note: the current revisions in Mercurial look a little weird because I had to split some Ruby 2.7-compatibility stuff away from the patch, and ran into a few conflicts. I'll fix it up before the next push.

This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants