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 11 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 :)

.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
Contributor

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.

@lalitb lalitb requested a review from a team as a code owner October 4, 2024 18:15
@cijothomas
Copy link
Member

@lalitb Are you planning to resurrect this? I think it makes sense to have a working prototype and merge it in, and mark the status as alpha.
Improvements can be done iteratively, so as to not block progress.

@lalitb
Copy link
Member Author

lalitb commented Feb 24, 2025

@lalitb Are you planning to resurrect this? I think it makes sense to have a working prototype and merge it in, and mark the status as alpha. Improvements can be done iteratively, so as to not block progress.

Yes, let me fix the CI, and make it ready. This would also help to get community contributions.

version = "0.1.0"
edition = "2021"
keywords = ["opentelemetry", "journald", "logs", "tracing"]
description = "OpenTelemetry logs exporter for journald"
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
description = "OpenTelemetry logs exporter for journald"
description = "OpenTelemetry Logs Exporter for journald"

}
}

fn get_priority(severity: &Severity) -> i32 {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
fn get_priority(severity: &Severity) -> i32 {
const fn get_priority(severity: &Severity) -> i32 {

.skip_while(|&c| c == '_')
.filter(|&c| c == '_' || c.is_ascii_alphanumeric())
.collect::<String>()
.to_uppercase()
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we could avoid an allocation related to uppercase conversion by mapping the characters to upper case before collecting them into a String.

name.chars()
        .map(|c| if c == '.' { '_' } else { c })
        .skip_while(|&c| c == '_')
        .filter(|&c| c == '_' || c.is_ascii_alphanumeric())
        .map(|c| c.to_ascii_uppercase())
        .collect::<String>()

.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
Contributor

Choose a reason for hiding this comment

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

Same as #84 (comment)

let send_result = self.send_to_journald(&iovecs);

if size_exceeded {
return Err(std::io::Error::new(
Copy link
Contributor

Choose a reason for hiding this comment

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

Since the default message size limit is zero, does it mean that for a user who did not configure the message size limit explicitly, this method would always return an error?

@@ -0,0 +1,28 @@
[package]
name = "opentelemetry-journald-logs"
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
name = "opentelemetry-journald-logs"
name = "opentelemetry-journald"

Copy link
Contributor

Choose a reason for hiding this comment

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

How about opentelemetry-exporter-journald?

Suggested change
name = "opentelemetry-journald-logs"
name = "opentelemetry-exporter-journald"

Copy link
Member

Choose a reason for hiding this comment

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

I like specific names, so opentelemetry-exporter-journald sounds good

opentelemetry = { workspace = true, features = ["logs"] }
opentelemetry_sdk = { workspace = true, features = ["logs"] }
libc = "0.2"
async-trait = "0.1"
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this needed?

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