blob: 8630eb9d1e876017c97b6b178e181f24322dfca3 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//! Log record items.
use crate::{
common::system_time::{fetch_time, TimePeriod},
proto::v3::{
log_data_body::Content, JsonLog, KeyStringValuePair, LogData, LogDataBody, LogTags,
TextLog, TraceContext, YamlLog,
},
trace::{
span::{HandleSpanObject, Span},
trace_context::TracingContext,
},
};
use std::time::{SystemTime, UNIX_EPOCH};
/// Log record type of [LogRecord];
pub enum RecordType {
/// Text type.
Text,
/// Json type.
Json,
/// Yaml type.
Yaml,
}
impl Default for RecordType {
fn default() -> Self {
Self::Text
}
}
/// The builder of [LogData];
#[derive(Default)]
pub struct LogRecord {
time: Option<SystemTime>,
is_ignore_time: bool,
endpoint: String,
tags: Vec<(String, String)>,
trace_id: Option<String>,
trace_segment_id: Option<String>,
span_id: Option<i32>,
record_type: RecordType,
content: String,
}
impl LogRecord {
/// New default [LogRecord];
#[inline]
pub fn new() -> Self {
Default::default()
}
/// Use custom time rather than now time.
#[inline]
pub fn custom_time(mut self, time: SystemTime) -> Self {
self.time = Some(time);
self
}
/// Not set the log time, OAP server would use the received timestamp as
/// log's timestamp, or relies on the OAP server analyzer.
#[inline]
pub fn ignore_time(mut self) -> Self {
self.is_ignore_time = true;
self
}
/// The logic name represents the endpoint, which logs belong.
#[inline]
pub fn endpoint(mut self, endpoint: impl Into<String>) -> Self {
self.endpoint = endpoint.into();
self
}
/// The available tags. OAP server could provide search/analysis
/// capabilities based on these.
pub fn add_tag(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.tags.push((key.into(), value.into()));
self
}
/// The available tags. OAP server could provide search/analysis
/// capabilities based on these.
pub fn add_tags<K, V, I>(mut self, tags: I) -> Self
where
K: Into<String>,
V: Into<String>,
I: IntoIterator<Item = (K, V)>,
{
self.tags
.extend(tags.into_iter().map(|(k, v)| (k.into(), v.into())));
self
}
/// Logs with trace context.
pub fn with_tracing_context(mut self, tracing_context: &TracingContext) -> Self {
self.trace_id = Some(tracing_context.trace_id().to_owned());
self.trace_segment_id = Some(tracing_context.trace_segment_id().to_owned());
self
}
/// The span should be unique in the whole segment.
pub fn with_span(mut self, span: &Span) -> Self {
self.span_id = Some(span.span_id());
self
}
/// A type to match analyzer(s) at the OAP server.
/// The data could be analyzed at the client side, but could be partial.
pub fn record_type(mut self, record_type: RecordType) -> Self {
self.record_type = record_type;
self
}
/// Log content.
pub fn content(mut self, content: impl Into<String>) -> Self {
self.content = content.into();
self
}
pub(crate) fn convert_to_log_data(
self,
service_name: String,
instance_name: String,
) -> LogData {
let timestamp = if self.is_ignore_time {
0
} else {
match self.time {
Some(time) => time
.duration_since(UNIX_EPOCH)
.map(|dur| dur.as_millis() as i64)
.unwrap_or_default(),
None => fetch_time(TimePeriod::Log),
}
};
let trace_context = match (self.trace_id, self.trace_segment_id, self.span_id) {
(Some(trace_id), Some(trace_segment_id), Some(span_id)) => Some(TraceContext {
trace_id,
trace_segment_id,
span_id,
}),
_ => None,
};
let tags = if self.tags.is_empty() {
None
} else {
let data = self
.tags
.into_iter()
.map(|(key, value)| KeyStringValuePair { key, value })
.collect();
Some(LogTags { data })
};
LogData {
timestamp,
service: service_name,
service_instance: instance_name,
endpoint: self.endpoint,
body: Some(LogDataBody {
r#type: "".to_owned(),
content: match self.record_type {
RecordType::Text => Some(Content::Text(TextLog { text: self.content })),
RecordType::Json => Some(Content::Json(JsonLog { json: self.content })),
RecordType::Yaml => Some(Content::Yaml(YamlLog { yaml: self.content })),
},
}),
trace_context,
tags,
layer: Default::default(),
}
}
}