use std::fs::{File, OpenOptions};
use std::io::{Read as _, Seek, SeekFrom, Write};
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use serde_json::{Map, Value};
use crate::bounds::bound_payload;
use crate::clock::now_rfc3339;
use crate::envelope::{ArtifactRef, Envelope, Kind};
use crate::filter::Filter;
use crate::redact::Redactor;
use crate::vocabulary::Vocabulary;
#[derive(Debug, thiserror::Error)]
pub enum EmitterError {
#[error("cannot record a {kind} event on stream {stream}: {source}")]
Write {
stream: String,
kind: Kind,
#[source]
source: std::io::Error,
},
#[error("cannot record a {kind} event on stream {stream} at seq {seq}: it does not serialize to JSON: {source}")]
Serialize {
stream: String,
seq: u64,
kind: Kind,
#[source]
source: serde_json::Error,
},
#[error("cannot order a {kind} event in {path}: {source}")]
Lock {
path: PathBuf,
kind: Kind,
#[source]
source: std::io::Error,
},
}
#[derive(Debug)]
pub struct Unrecorded<V: Vocabulary> {
pub envelope: Envelope<V>,
pub error: EmitterError,
}
impl<V: Vocabulary> std::fmt::Display for Unrecorded<V> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.error.fmt(f)
}
}
impl<V: Vocabulary> std::error::Error for Unrecorded<V> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.error)
}
}
enum Sink {
Single {
seq: Arc<AtomicU64>,
writer: Arc<Mutex<Box<dyn Write + Send>>>,
},
Shared { path: PathBuf },
}
pub struct Emitter<V: Vocabulary> {
stream: String,
source: V::Source,
version: u32,
sink: Arc<Sink>,
labels: V::Labels,
dimensions: V::Dimensions,
filter: Arc<Filter<V>>,
redactor: Arc<Redactor>,
}
impl<V: Vocabulary> Clone for Emitter<V> {
fn clone(&self) -> Self {
Self {
stream: self.stream.clone(),
source: self.source.clone(),
version: self.version,
sink: Arc::clone(&self.sink),
labels: self.labels.clone(),
dimensions: self.dimensions.clone(),
filter: Arc::clone(&self.filter),
redactor: Arc::clone(&self.redactor),
}
}
}
impl<V: Vocabulary> std::fmt::Debug for Emitter<V> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Emitter")
.field("stream", &self.stream)
.field("source", &self.source)
.field("version", &self.version)
.finish_non_exhaustive()
}
}
impl<V: Vocabulary> Emitter<V> {
#[must_use]
pub fn new(stream: impl Into<String>, source: V::Source, sink: Box<dyn Write + Send>) -> Self {
Self::build(
stream.into(),
source,
Sink::Single {
seq: Arc::new(AtomicU64::new(0)),
writer: Arc::new(Mutex::new(sink)),
},
)
}
#[must_use]
pub fn shared(stream: impl Into<String>, source: V::Source, path: impl AsRef<Path>) -> Self {
Self::build(
stream.into(),
source,
Sink::Shared {
path: path.as_ref().to_path_buf(),
},
)
}
fn build(stream: String, source: V::Source, sink: Sink) -> Self {
let version = V::write_version(&source);
Self {
stream,
source,
version,
sink: Arc::new(sink),
labels: V::Labels::default(),
dimensions: V::Dimensions::default(),
filter: Arc::new(Filter::default()),
redactor: Arc::new(Redactor::from_env()),
}
}
#[must_use]
pub fn with_version(mut self, version: u32) -> Self {
self.version = version;
self
}
#[must_use]
pub fn with_filter(mut self, filter: Filter<V>) -> Self {
self.filter = Arc::new(filter);
self
}
#[must_use]
pub fn with_redactor(mut self, redactor: Redactor) -> Self {
self.redactor = Arc::new(redactor);
self
}
#[must_use]
pub fn with_labels(mut self, labels: V::Labels) -> Self {
self.labels = merged(&labels, self.labels.clone());
self
}
#[must_use]
pub fn with_dimensions(mut self, dimensions: V::Dimensions) -> Self {
self.dimensions = dimensions;
self
}
#[must_use]
pub fn stream(&self) -> &str {
&self.stream
}
#[must_use]
pub fn source(&self) -> &V::Source {
&self.source
}
#[must_use]
pub fn version(&self) -> u32 {
self.version
}
#[must_use]
pub fn labels(&self) -> &V::Labels {
&self.labels
}
pub fn emit(&self, kind: impl Into<Kind>, payload: Map<String, Value>) -> Envelope<V> {
self.emit_with(kind, payload, Vec::new())
}
pub fn emit_with(
&self,
kind: impl Into<Kind>,
payload: Map<String, Value>,
artifacts: Vec<ArtifactRef>,
) -> Envelope<V> {
self.emit_stamped(kind, self.dimensions.clone(), payload, artifacts)
}
pub fn emit_stamped(
&self,
kind: impl Into<Kind>,
dimensions: V::Dimensions,
payload: Map<String, Value>,
artifacts: Vec<ArtifactRef>,
) -> Envelope<V> {
match self.try_emit_stamped(kind, dimensions, payload, artifacts) {
Ok(envelope) => envelope,
Err(unrecorded) => {
eprintln!("onemessagebus: warning: {}", unrecorded.error);
unrecorded.envelope
}
}
}
pub fn try_emit(
&self,
kind: impl Into<Kind>,
payload: Map<String, Value>,
artifacts: Vec<ArtifactRef>,
) -> Result<Envelope<V>, Box<Unrecorded<V>>> {
self.try_emit_stamped(kind, self.dimensions.clone(), payload, artifacts)
}
pub fn try_emit_stamped(
&self,
kind: impl Into<Kind>,
dimensions: V::Dimensions,
payload: Map<String, Value>,
artifacts: Vec<ArtifactRef>,
) -> Result<Envelope<V>, Box<Unrecorded<V>>> {
let kind = kind.into();
let payload = match self.redactor.redact_value(Value::Object(payload)) {
Value::Object(clean) => bound_payload(clean),
_ => Map::new(),
};
let admitted = self
.filter
.allows(&self.source, kind.as_str(), &dimensions, &self.labels);
let mut envelope = Envelope {
v: self.version,
ts: now_rfc3339(),
stream: self.stream.clone(),
seq: 0,
source: self.source.clone(),
kind,
dimensions,
labels: self.labels.clone(),
payload,
artifacts,
};
match self.sink.as_ref() {
Sink::Single { seq, writer } => {
envelope.seq = if admitted {
seq.fetch_add(1, Ordering::SeqCst) + 1
} else {
seq.load(Ordering::SeqCst) + 1
};
if !admitted {
return Ok(envelope);
}
let line = match line_of(&envelope) {
Ok(line) => line,
Err(failure) => return Err(self.unserializable(envelope, failure)),
};
let mut sink = writer
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
match sink.write_all(line.as_bytes()).and_then(|()| sink.flush()) {
Ok(()) => Ok(envelope),
Err(failure) => Err(self.failed(envelope, failure)),
}
}
Sink::Shared { path } => {
let mut file = match open_locked(path) {
Ok(file) => file,
Err(failure) => {
let error = EmitterError::Lock {
path: path.clone(),
kind: envelope.kind.clone(),
source: failure,
};
return Err(Box::new(Unrecorded { envelope, error }));
}
};
let held = match recorded(&mut file, path) {
Ok(held) => held,
Err(failure) => return Err(self.failed(envelope, failure)),
};
envelope.seq = held + 1;
if !admitted {
return Ok(envelope);
}
let line = match line_of(&envelope) {
Ok(line) => line,
Err(failure) => return Err(self.unserializable(envelope, failure)),
};
match file.write_all(line.as_bytes()).and_then(|()| file.flush()) {
Ok(()) => Ok(envelope),
Err(failure) => Err(self.failed(envelope, failure)),
}
}
}
}
fn failed(&self, envelope: Envelope<V>, failure: std::io::Error) -> Box<Unrecorded<V>> {
let error = EmitterError::Write {
stream: self.stream.clone(),
kind: envelope.kind.clone(),
source: failure,
};
Box::new(Unrecorded { envelope, error })
}
fn unserializable(
&self,
envelope: Envelope<V>,
failure: serde_json::Error,
) -> Box<Unrecorded<V>> {
let error = EmitterError::Serialize {
stream: self.stream.clone(),
seq: envelope.seq,
kind: envelope.kind.clone(),
source: failure,
};
Box::new(Unrecorded { envelope, error })
}
}
fn line_of<V: Vocabulary>(envelope: &Envelope<V>) -> serde_json::Result<String> {
let mut line = serde_json::to_string(envelope)?;
line.push('\n');
Ok(line)
}
fn open_locked(path: &Path) -> std::io::Result<File> {
let file = OpenOptions::new()
.create(true)
.truncate(false)
.read(true)
.write(true)
.open(path)?;
file.lock()?;
Ok(file)
}
fn recorded(file: &mut File, path: &Path) -> std::io::Result<u64> {
let mut contents = Vec::new();
file.seek(SeekFrom::Start(0))?;
file.read_to_end(&mut contents)?;
let whole = contents.iter().filter(|byte| **byte == b'\n').count() as u64;
if !contents.is_empty() && !contents.ends_with(b"\n") {
let keep = contents
.iter()
.rposition(|byte| *byte == b'\n')
.map_or(0, |at| at + 1);
file.set_len(keep as u64)?;
file.seek(SeekFrom::End(0))?;
eprintln!(
"onemessagebus: healed a torn record of {} bytes at byte {keep} of {}",
contents.len() - keep,
path.display()
);
}
Ok(whole)
}
fn merged<L: serde::Serialize + serde::de::DeserializeOwned + Clone>(base: &L, fill: L) -> L {
let (Ok(Value::Object(mut base_map)), Ok(Value::Object(fill_map))) =
(serde_json::to_value(base), serde_json::to_value(&fill))
else {
return base.clone();
};
for (key, value) in fill_map {
base_map.entry(key).or_insert(value);
}
serde_json::from_value(Value::Object(base_map)).unwrap_or_else(|_| base.clone())
}