-
Notifications
You must be signed in to change notification settings - Fork 1
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 Elasticsearch Log Exporter with tests #18
Conversation
2da8cda
to
4255ac1
Compare
Codecov Report
@@ Coverage Diff @@
## master #18 +/- ##
==========================================
+ Coverage 94.38% 94.45% +0.07%
==========================================
Files 185 187 +2
Lines 8014 8244 +230
==========================================
+ Hits 7564 7787 +223
- Misses 450 457 +7
|
0d650af
to
68e510c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Missing copyright header: None of the source files have the OpenTelemetry authors copyright. Please make sure all source files filed as PRs have this copyright header.
- Add header comments noting purpose of each source file
- Add inline function headers noting purpose of each function
79e6591
to
e1c34b1
Compare
e1c34b1
to
c2ef1af
Compare
record->SetAttribute("key2", "value2"); | ||
|
||
// Write the log record to the exporter, and time the duration | ||
auto t1 = std::chrono::high_resolution_clock::now(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would std::chrono::system_clock::now()
suffice?
{} | ||
|
||
std::unique_ptr<sdklogs::Recordable> ElasticsearchLogExporter::MakeRecordable() noexcept | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: if the exporter is shut down, does it still need to make a recordable? ie. could it return nullptr
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Saw your discussion on ostream, leaving as is
request->SetUri(options_.index_ + "/_bulk?pretty"); | ||
request->SetMethod(http_client::Method::Post); | ||
request->AddHeader("Content-Type", "application/json"); | ||
request->SetTimeoutMs(std::chrono::milliseconds(1000 * options_.response_timeout_)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: could just use microseconds
instead of multiplying by 1000?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was using seconds because http timeouts can be fairly large (like 30 seconds), so typing 30 makes more sense than 30000 imo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean would request->SetTimeoutMs(std::chrono::microseconds(options_.response_timeout_));
work?
Edit: ignore this, woul dneed std::duration_cast
static_cast<ElasticSearchRecordable *>(record.release())); | ||
body += json_record->GetJSON().dump() + "\n"; | ||
} | ||
std::vector<uint8_t> body_vec(body.begin(), body.end()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: why is body set as a uint8_t
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in Lalit's http client interface the body is represented internally as a vector of uint8_t
, so I want to keep consistent with that
*/ | ||
std::string GetResponseBody() | ||
{ | ||
if (!response_received_) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: is it possible to check if response_received
before entering this function instead? not sure if setting a random response body here is good practice or
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yea it's being checked in the export method already, I had this as a just in case kinda thing tho. I'll take it out tho since it's never gonna get called
bool response_received_ = false; | ||
|
||
// A string to store the response body | ||
std::string body_ = ""; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i don't think this needs a default initialization? the default string ctor should be fine :)
request->SetTimeoutMs(std::chrono::milliseconds(1000 * options_.response_timeout_)); | ||
|
||
// Add the request body | ||
std::string body = ""; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this variable not already initialized as a private member variable? (see comment above) does this overwrite it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nope, the body member variable is for the response classes response body, and this is for the request body
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ohh i see, could they maybe be named like response_body
and request_body
? idk if you think thats necessary
{ | ||
// Create options for the elasticsearch exporter | ||
logs_exporter::ElasticsearchExporterOptions options("localhost", -1, "logs", 5, true); | ||
options.response_timeout_ = 10; // Wait 10 seconds to receive a response |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this overkill? (if the CI needed to wait 10 seconds while running this test, I'm just wondering if this will be a problem)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Considering Lalit's http unit test does this except with 30 seconds, I think its fine :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ohh I see yeah, I get wait during the CI and I'm always afraid it's deadlocking or sth, gotcha gotcha
auto exporter = | ||
std::unique_ptr<sdklogs::LogExporter>(new logs_exporter::ElasticsearchLogExporter); | ||
bool shutdownResult = exporter->Shutdown(); | ||
ASSERT_TRUE(shutdownResult); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: EXPECT_TRUE(exporter->Shutdown());
?
|
||
// Ensure the timeout is within the range of the timeout specified ([10, 10 + 1] seconds) | ||
auto duration = std::chrono::duration_cast<std::chrono::seconds>(t2 - t1).count(); | ||
ASSERT_TRUE((duration >= options.response_timeout_) && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: i'm confused why the timeout here has such a strict lowerbound? (i thought anything greater than 0 would be acceptable) - is it sufficient to just check ASSERT_TRUE(duration < options.response_timeout_ +1)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here I'm checking that it waits for the timeout instead of immediately returning failure. Its important for this exporter to wait for at least the timeout to give time for a connection/response to come.
* @param host The host of the Elasticsearch instance | ||
* @param port The port of the Elasticsearch instance | ||
* @param index The index/shard that the logs will be written to | ||
* @param response_timeout The maximum time the exporter should wait after sending a request to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: specify whether this timeout is in milliseconds, microseconds, etc?
ElasticsearchExporterOptions(std::string host = "localhost", | ||
int port = 9200, | ||
std::string index = "logs", | ||
int response_timeout = 30, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
related nit: would it be better just to use std::chrono::milliseconds
/microseconds
here isntead of int
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also curious- what's the difference/purpose of passing in the params separately into the constructor, as opposed to just the plain old constructor with the options
struct?
/** | ||
* Exports a vector of log records to the Elasticsearch instance. Guaranteed to return after a | ||
* timeout specified from the options passed from the constructor. | ||
* @param records A list of log records to send to Elasticsearch. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: because it might be confusing log records
with the LogRecord
recordable: change comment to say just a list of "logs"?
// Return failure if this exporter has been shutdown | ||
if (isShutdown_) | ||
{ | ||
if (options_.console_debug_) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: add {}
around this if block too?
{ | ||
// TODO: retry logic | ||
|
||
if (options_.console_debug_) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{}
here (i forget which place i commented on in the upstream PR :p)
* localhost:9200/logs with a timeout of 30 seconds and disabled console debugging | ||
* @param host The host of the Elasticsearch instance | ||
* @param port The port of the Elasticsearch instance | ||
* @param index The index/shard that the logs will be written to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just wondering - should this index
variable also be used in the body_
variable? which currently has the default index of index:{}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the way elasticsearch works is you connect to it with an endpoint, like localhost::3000/logs
for example. The /logs
part is the index, so having the index:{}
means that it will use the index specified in the endpoint instead of a specific one.
|
||
private: | ||
// Stores if this exporter had its Shutdown() method called | ||
bool isShutdown_ = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
another nit: is_shutdown_
for google naming convention
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm also just wondering through (relates to the ostream exporter too) - do you think this var should be initialized in the ctor instead of here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm having it here means we don't have to set it in all the different constructors (this one has 2), but I'm also not sure about if OTEL encourages this... I'll ask during the next maintainers meeting!
* @param index The index/shard that the logs will be written to | ||
* @param response_timeout The maximum time the exporter should wait after sending a request to | ||
* Elasticsearch | ||
* @param console_debug Print the status of the exporter methods in the console |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: rename var to console_debug_on
to indicte it's a bool? and maybe the comment could be: "whether or not this exporter prints [...]", so it indicates this is a flag to be toggled
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To me, if there's a bool variable called console_debug its pretty explicit that turning it on is going to enable console debugging. So I think leaving it as is is cool
/** | ||
* Returns a JSON object contain the log information | ||
*/ | ||
nlohmann::json GetJSON() noexcept { return json_; }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: GetJSONRecord()
?
Sorry for all the comments - "nit" has also become my favorite word :) |
f7dda89
to
6fd1289
Compare
056e9cc
to
0526050
Compare
This PR adds an Elasticsearch exporter for logs, which is a requirement from issue #337. This includes:
Some overlap exists with the OStream exporter PR (open-telemetry#430) currently, but will be addressed in a future PR.
Notes:
cc @xukaren @alolita