Skip to content

init4tech/bin-base

Repository files navigation

init4 bin-base

Crates.io Documentation CI License: MIT

Common functionality for binaries produced by the init4 project. This crate provides:

  • Environment parsing utilities
  • Standard tracing setup with otlp support
  • Standard server for Prometheus metrics
  • Standard environment variables to configure these features

Note: This crate is intended as a base for all binaries in the init4 project. It is not intended for outside consumption.

Installation

[dependencies]
init4-bin-base = "0.18"

Quick Start

use init4_bin_base::init4;

fn main() {
    init4();
    // your code here
}

Build the crate docs with cargo doc --open for more details.


init4 Tracing Best Practices

Carefully Consider Level

Event and span level should correspond to the significance of the event as follows:

Level Usage Examples
TRACE Low-level, detailed debugging info. Use rarely. HTTP request body, every network packet
DEBUG Low-level lifecycle info useful for debugging. Use sparingly. Single DB query result, single function call result
INFO Normal operation lifecycle info. Default level for most events. Request processing start, DB connection established
WARN Potential problems that don't prevent operation. Request took longer than expected, ignored parse error
ERROR Problems that prevent correct operation. DB connection failed, required file not found

By default, the OTLP exporter captures DEBUG and higher. Configure with OTEL_LEVEL env var. The log formatter logs at INFO level. Configure with RUST_LOG env var.

// ❌ Avoid
warn!("Connected to database");

// ✅ Instead
info!("Connected to database");

Import from bin-base

Re-export all necessary crates from init4-bin-base rather than adding them to your Cargo.toml:

// ❌ Avoid
use tracing::info;

// ✅ Instead
use init4_bin_base::deps::tracing::info;

Spans

Spans represent the duration of a unit of work. They should be:

  • Time-limited — at most a few seconds
  • Work-associated — tied to a specific action
  • Informative — have useful data, not over-verbose

Inheritance

Spans inherit the currently-entered span as their parent. Avoid spurious span relationships:

// ❌ Avoid — accidental parent-child relationship
let span = info_span!("outer_function").entered();
let my_closure = || {
    let span = info_span!("accidental_child").entered();
    // do some work
};
do_work(closure);

// ✅ Instead — closure span created before outer span
let my_closure = || {
    let span = info_span!("not_a_child").entered();
    // do some work
};
let span = info_span!("outer_function").entered();
do_work(closure);

Avoid Over-Verbose Spans

When instrumenting methods, skip self and add only needed fields:

// ❌ Avoid — self will be Debug-printed (verbose)
#[instrument]
async fn my_method(&self) { }

// ✅ Instead — skip self, add specific fields
#[instrument(skip(self), fields(self.id = self.id))]
async fn my_method(&self) { }

For multiple arguments, skip all and add back what you need:

// ❌ Avoid
#[instrument]
async fn my_method(&self, arg1: i32, arg2: String) { }

// ✅ Instead
#[instrument(skip_all, fields(arg1))]
async fn my_method(&self, arg1: i32, arg2: String) { }

Instrument Futures, Not JoinHandles

// ❌ Avoid — span won't propagate to the future
tokio::spawn(fut).instrument(span);

// ✅ Instead
tokio::spawn(fut.instrument(span));

Instrument Work, Not Tasks

Avoid adding spans to long-running tasks. Create spans in the internal loop instead:

// ❌ Avoid — span open for entire task lifetime
let span = info_span!("task");
tokio::spawn(async {
    loop {
        // work
        tokio::time::sleep(Duration::from_secs(1)).await;
    }
}.instrument(span));

// ✅ Instead — span per iteration
tokio::spawn(async {
    loop {
        let span = info_span!("loop_iteration").entered();
        // work
        drop(span);
        tokio::time::sleep(Duration::from_secs(1)).await;
    }
});

Root Spans

Root spans are top-level spans in a trace. Ensure they correspond to a SINGLE UNIT OF WORK:

// ❌ Avoid — nested work units under one span
let span = info_span!("task");
for item in my_vec {
    let work_span = info_span!("work_unit").entered();
    // work
}

// ✅ Instead — each work unit is a root span
let work_loop = info_span!("work_loop").entered();
for item in my_vec {
    let span = info_span!(parent: None, "work_unit").entered();
    // work
    drop(span);
}

With #[instrument]:

// ✅ Create root span
#[instrument(parent = None)]
async fn a_unit_of_work() { }

Be Careful with instrument(err)

Using #[instrument(err)] emits errors at EACH span level. Only root spans should have instrument(err):

// ❌ Avoid — error emitted multiple times
#[instrument(err)]
async fn one() -> Result<(), ()> { }

#[instrument(err)]
async fn two() -> Result<(), ()> {
    one().await?;
}

// ✅ Instead — only root span has err
#[instrument]
async fn one() -> Result<(), ()> { }

#[instrument(parent = None, err)]
async fn two() -> Result<(), ()> {
    one().await?;
}

To track error bubbling, record additional info:

#[instrument(err)]
async fn do_thing() -> std::io::Result<()> {
    do_inner().await.inspect_err(|_| {
        tracing::span::Span::current().record("err_source", "do_inner");
    })
}

Managing Events

Events represent state at a single point in time. They should be:

  • Informative — useful data, not over-verbose
  • Descriptive — clear, concise messages
  • Lifecycle-aware — record lifecycle of a unit of work
  • Non-repetitive — fire ONCE in a span's lifetime

Avoid String Interpolation

Events are structured data. String interpolation loses type information:

// ❌ Avoid
info!("Value calculated: {}", x);

// ✅ Instead
info!(x, "Value calculated");

Lifecycle Events

Events should capture significant lifecycle steps, not every step:

// ❌ Avoid — using events for start/end
info!("Parsing input");
let parsed = parse_input(input);
info!("Input parsed");

// ✅ Instead — use spans
let span = info_span!("parse_input").entered();
let parsed = parse_input(input);
drop(span);

// ✅ Even better — use #[instrument]
#[instrument(skip(input), fields(input_size = input.len()))]
fn parse_input(input: String) -> Option<ParsedInput> { }

DRY: Don't Repeat Yourself (at INFO and DEBUG)

If firing the same event many times, you're violating span rules or verbosity rules:

// ❌ Avoid — same event many times
for i in my_vec {
    info!(i, "processing");
    do_work(i);
}

// ✅ Instead — trace per item, info for summary
for i in my_vec {
    do_work(i);
    trace!(i, "processed vec item");
}
info!(my_vec.len(), "processed my vec");

License

This project is licensed under the MIT License.

About

signet binary base. exports and common observability deps

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages