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

Implement journald exporter for OpenTelemetry #84

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

lalitb
Copy link
Member

@lalitb lalitb commented Jun 25, 2024

Changes

This adds Journald log exporter for OpenTelemetry, allowing logs to be sent to Journald. Note that this exporter is experimental, and breaking changes are expected. The performance and stability improvements are required, and contributions are welcome.

Features:

  • Export OpenTelemetry logs to journald.
  • Optionally serialize logs and attributes as JSON.
  • Configurable message size limit based on the journald configuration : As of now this is just used to return an error, in future memfd can be used for sending large messages.
  • Configurable attribute prefix.

Dependencies:

This implementation relies on the libsystemd library for sending log entries to the journald daemon. It uses sd_journal_sendv API method provided by the library.

Merge requirement checklist

  • CONTRIBUTING guidelines followed
  • Unit tests added/updated (if applicable)
  • Appropriate CHANGELOG.md files updated for non-trivial, user-facing changes
  • Changes in public API reviewed (if applicable)

@lalitb lalitb requested a review from a team June 25, 2024 01:04
@lalitb
Copy link
Member Author

lalitb commented Jun 25, 2024

@TommyCpp had an question on comparing it with the subscriber provided by Tokio tracing library - https://github.com/tokio-rs/tracing/blob/master/tracing-journald/ -

  • The key distinction in the implementation is that the tracing journald subscriber writes directly to the Unix-domain socket monitored by the journald daemon. In contrast, this PR leverages the API provided by the libsystemd library (formerly libsystemd-journald) to perform the same operation. @Ralith (apologies for the tag) added the initial tracing implementation, so he might offer some background on the direct socket approach and any concerns related to using libsystemd?
  • The tracing subscriber supports writing large logs (beyond limit enforced by journald config) using memfd, which can be added in subsequent implementation.
  • I haven't done the benchmarks yet to compare, need to do this.

@Ralith
Copy link

Ralith commented Jun 26, 2024

background on the direct socket approach and any concerns related to using libsystemd

This was motivated by the improved downstream developer experience from being pure Rust, and the simplicity of the journald client protocol. It's not significantly more complicated than FFIing to libsystemd, and it dramatically reduces the sensitivity of downstream crates to build environment issues like missing pkg-config files, linking during cross-compilation, etc.

@lalitb
Copy link
Member Author

lalitb commented Jun 26, 2024

This was motivated by the improved downstream developer experience from being pure Rust, and the simplicity of the journald client protocol. It's not significantly more complicated than FFIing to libsystemd, and it dramatically reduces the sensitivity of downstream crates to build environment issues like missing pkg-config files, linking during cross-compilation, etc.

Thanks, @Ralith, for the background. The concerns regarding build complexities with FFI to C are valid, and we will consider documenting this for the developers. My motivation for using libsystemd is that it internally handles sending large data through memfd, and so abstracting these low-level details. And hopefully being future-proof with any protocol changes :)

self
}

pub fn attribute_prefix(mut self, attribute_prefix: Option<String>) -> Self {
Copy link

Choose a reason for hiding this comment

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

It would be more user friendly to just ask them for a string slice instead of having them to call to_string() and wrapping it with an Option.

Suggested change
pub fn attribute_prefix(mut self, attribute_prefix: Option<String>) -> Self {
pub fn attribute_prefix(mut self, attribute_prefix: &str) -> Self {

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

}

pub fn message_size_limit(mut self, message_size_limit: usize) -> Self {
self.message_size_limit = Some(message_size_limit);
Copy link

Choose a reason for hiding this comment

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

Add some validation? Something like message_size_limit > 0?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

}
#[cfg(not(feature = "json"))]
{
return Err(std::io::Error::new(
Copy link

@utpilla utpilla Jul 1, 2024

Choose a reason for hiding this comment

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

Move this validation to json_format method where the user configures this bool variable instead of checking it in export method?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

let identifier = self.identifier.ok_or("Identifier is required")?;
let message_size_limit = self
.message_size_limit
.ok_or("Message size limit is required")?;
Copy link

Choose a reason for hiding this comment

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

Could we have a default value for message_size_limit instead of requiring the user to always specify this?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, default is 0 now, which means no limit is enforced.

self
}

pub fn build(self) -> Result<JournaldLogExporter, &'static str> {
Copy link

Choose a reason for hiding this comment

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

Could we simply return JournadLogExporter instead of a Result?

Suggested change
pub fn build(self) -> Result<JournaldLogExporter, &'static str> {
pub fn build(self) -> JournaldLogExporter {

Copy link
Member Author

Choose a reason for hiding this comment

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

Done


fn init_logger() -> LoggerProvider {
let exporter = JournaldLogExporter::builder()
.identifier("opentelemetry-journal-exporter")
Copy link

Choose a reason for hiding this comment

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

Assign with prefix to the builder methods?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

.identifier("opentelemetry-journal-exporter")
.message_size_limit(4 * 1024)
.attribute_prefix(Some("OTEL_".to_string()))
.json_format(true) //uncomment to log in json format
Copy link

Choose a reason for hiding this comment

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

The comment is unclear.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch, fixed now.

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.

3 participants