Skip to content

Commit

Permalink
Hello World
Browse files Browse the repository at this point in the history
Signed-off-by: Vincenzo Palazzo <[email protected]>
  • Loading branch information
vincenzopalazzo committed Oct 1, 2024
1 parent b5a24d1 commit 67c6e73
Show file tree
Hide file tree
Showing 7 changed files with 318 additions and 2 deletions.
54 changes: 54 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Sanity Check codebase

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
check:
name: Build
runs-on: ubuntu-latest
strategy:
matrix:
node: [stable, beta, nightly]
steps:
- name: Checkout sources
uses: actions/checkout@v2

- name: Install toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.node }}
override: true

- name: Run cargo check
uses: actions-rs/cargo@v1
with:
command: check

- name: Run cargo test
run: make check

lints:
name: Lints
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2

- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
components: rustfmt, clippy

- name: Run cargo fmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
18 changes: 18 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "opentelemetry-common"
version = "0.1.0"
edition = "2021"

[dependencies]
opentelemetry = { version = "0.25", features = ["logs"] }
opentelemetry-appender-log = { version = "0.25", default-features = false }
opentelemetry_sdk = { version = "0.25", features = [ "logs", "rt-tokio" ] }
opentelemetry-otlp = { version = "0.25", features = [ "http-proto", "reqwest-client", "reqwest-rustls", "logs" ] }
opentelemetry-semantic-conventions = { version = "0.25.0" }
anyhow = "^1"
log = { version = "0.4", features = ["std"] }

[dev-dependencies]
clap = { version = "4.0.26", features = ["derive"] }
tokio = { version = "^1.29.1", features = ["rt-multi-thread", "parking_lot"] }
env_logger = "0.11.3"
5 changes: 3 additions & 2 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -290,8 +290,9 @@ to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
A minimal and simple opentelemetry log adapter that allow you to
export your rust log to an opentelemetry collector.
Copyright (C) 2024 Vincenzo Palazzo <[email protected]>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Expand Down
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,32 @@
# opentelemetry-log
A minimal and simple opentelemetry log adapter that allow you to export your rust log to an opentelemetry collector
## Features

- Export Rust logs to an OpenTelemetry collector
- Minimal and simple adapter
- Easy integration with existing logging (just `log` for now) frameworks

## Installation

Add this to your `Cargo.toml`:

```toml
[dependencies]
opentelemetry-log = "0.1"
```

## Usage

```rust
use opentelemetry_common::Opentelemetry;

fn main() {
let mut manager = Opentelemetry::new();
manager.init_log("example", &args.level, &url)?;
// Your application code
}
```

## License

This project is licensed under the GNU GENERAL PUBLIC LICENSE. See the [LICENSE](LICENSE) file for details.
33 changes: 33 additions & 0 deletions examples/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use clap::Parser;

use opentelemetry_common::Opentelemetry;

#[derive(Debug, Parser)]
#[clap(name = "opentelemetry.rs")]
pub struct Args {
#[clap(short, long, value_parser)]
pub url: String,
#[clap(short, long, value_parser)]
pub message: String,
#[clap(short, long)]
pub level: String,
}

// the async main is not required by our application
// but the opentelemetry app is requiring to be
// in an async context, so we use this
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let args = Args::parse();
let url = args.url;

let mut manager = Opentelemetry::new();
manager.init_log("example", &args.level, &url)?;

match args.level.as_str() {
"info" => log::info!("{}", args.message),
"debug" => log::debug!("{}", args.message),
_ => anyhow::bail!("level `{}` not found", args.level),
}
Ok(())
}
96 changes: 96 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
//! # OpenTelemetry Log Integration
//!
//! This crate provides integration with OpenTelemetry for logging purposes. It allows you to
//! initialize and manage loggers that are compatible with the OpenTelemetry SDK.
//!
//! ## Features
//!
//! - Initialize loggers with specific tags, levels, and exporter endpoints.
//! - Automatically manage the lifecycle of loggers, ensuring proper shutdown.
//!
//! ## Usage
//!
//! Add this crate to your `Cargo.toml`:
//!
//! ```toml
//! [dependencies]
//! opentelemetry-log = "0.1"
//! ```
//!
//! Import and use the `Opentelemetry` struct to manage your loggers:
//!
//! ```rust
//! use opentelemetry_log::Opentelemetry;
//!
//! fn main() {
//! let mut otel = Opentelemetry::new();
//! otel.init_log("my_app", "info", "http://localhost:4317").unwrap(); // Please do not unwrap in production code
//! // Your application logic here
//! }
//! ```
//!
//! ## Modules
//!
//! - `log`: Contains the log initialization logic.
//!
//! ## Structs
//!
//! - `Opentelemetry`: Main struct for managing OpenTelemetry loggers.
//!
//! ## Traits
//!
//! - `Default`: Provides a default implementation for `Opentelemetry`.
//! - `Drop`: Ensures proper shutdown of loggers when `Opentelemetry` instances are dropped.
//!
//! ## Errors
//!
//! This crate uses the `anyhow` crate for error handling. Ensure you handle errors appropriately
//! when initializing and using loggers.
pub mod log;
pub use anyhow;

use std::sync::Arc;

use opentelemetry_sdk::logs as sdklogs;

/// Main struct for managing OpenTelemetry loggers, when you init the logger
/// remember to keep this alive for all the lifetime of the application.
///
/// An example can be found in the `examples` directory.
#[derive(Debug, Clone)]
pub struct Opentelemetry {
pub(crate) logger: Option<Arc<sdklogs::LoggerProvider>>,
}

impl Default for Opentelemetry {
fn default() -> Self {
Self::new()
}
}

impl Opentelemetry {
pub fn new() -> Self {
Opentelemetry { logger: None }
}

/// Initialize a new logger with the provided tag, level, and exporter endpoint.
/// this is assuming tat your application is using `log` crate
pub fn init_log(
&mut self,
tag: &str,
level: &str,
exporter_endpoint: &str,
) -> anyhow::Result<()> {
log::init(self, tag.to_owned(), level, exporter_endpoint)?;
Ok(())
}
}

impl Drop for Opentelemetry {
fn drop(&mut self) {
let Some(Err(err)) = self.logger.as_ref().map(|log| log.shutdown()) else {
return;
};
panic!("Failed to shutdown logger: {:?}", err);
}
}
84 changes: 84 additions & 0 deletions src/log.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//! This module provides functionality to initialize and configure a logger that exports logs
//! using OpenTelemetry. The logger can be configured with different levels and exporter endpoints.
//!
//! # Functions
//!
//! - `init`: Initializes a new logger exported with OpenTelemetry. It sets up the logger provider,
//! configures the log appender, and sets the global logger.
//! - `http_exporter`: Creates a new HTTP exporter builder for OpenTelemetry.
//!
//! # Usage
//!
//! To use this module, call the `init` function with the appropriate parameters to set up the logger.
//! The logger will then export logs to the specified endpoint using the configured protocol.
//!
//! # Example
//!
//! ```rust
//! use crate::log::init;
//! use crate::Opentelemetry;
//!
//! let mut manager = Opentelemetry::new();
//! let tag = "my_service".to_string();
//! let level = "info";
//! let exporter_endpoint = "http://localhost:4317";
//!
//! init(&mut manager, tag, level, exporter_endpoint).expect("Failed to initialize logger");
//! ```
//!
//! # Dependencies
//!
//! This module depends on the following crates:
//!
//! - `opentelemetry`
//! - `opentelemetry_appender_log`
//! - `opentelemetry_otlp`
//! - `opentelemetry_sdk`
//! - `log`
//! - `anyhow`
use std::str::FromStr;
use std::sync::Arc;

use opentelemetry::KeyValue;
use opentelemetry_appender_log::OpenTelemetryLogBridge;
use opentelemetry_otlp::HttpExporterBuilder;
use opentelemetry_otlp::Protocol;
use opentelemetry_otlp::WithExportConfig;
use opentelemetry_sdk::Resource;

use crate::Opentelemetry;

/// Initialize a new logger exported with open telemetry.
pub fn init(
manager: &mut Opentelemetry,
tag: String,
level: &str,
exporter_endpoint: &str,
) -> anyhow::Result<()> {
let logger_provider = opentelemetry_otlp::new_pipeline()
.logging()
.with_resource(Resource::new(vec![KeyValue::new(
opentelemetry_semantic_conventions::resource::SERVICE_NAME,
tag,
)]))
.with_exporter(
http_exporter()
.with_protocol(Protocol::HttpBinary) //can be changed to `Protocol::HttpJson` to export in JSON format
.with_endpoint(format!("{exporter_endpoint}/v1/logs")),
)
.install_batch(opentelemetry_sdk::runtime::Tokio)?;
manager.logger = Some(Arc::new(logger_provider.clone()));

// Setup Log Appender for the log crate.
let otel_log_appender = OpenTelemetryLogBridge::new(&logger_provider);

// the install method set a global provider, that we can use now
log::set_boxed_logger(Box::new(otel_log_appender)).map_err(|err| anyhow::anyhow!("{err}"))?;
let level = log::Level::from_str(level)?;
log::set_max_level(level.to_level_filter());
Ok(())
}

fn http_exporter() -> HttpExporterBuilder {
opentelemetry_otlp::new_exporter().http()
}

0 comments on commit 67c6e73

Please sign in to comment.