Files
Genarrative/server-rs/crates/shared-logging/src/lib.rs
T
JenkenB ae014ac881 Switch to VectorEngine gpt-image-2 and edits
Replace uses of the legacy `gpt-image-2-all` model with `gpt-image-2` and standardize image workflows: no-reference generation uses POST /v1/images/generations, any-reference flows use POST /v1/images/edits with multipart `image` parts. Update SKILLs, generation scripts, decision logs, and docs to reflect the contract change and edits-vs-generations guidance. Apply corresponding changes across backend (api-server match3d/puzzle modules, openai image adapter, mappers, telemetry, spacetime client/module), frontend components and services (Match3D, Puzzle, CreativeImageInputPanel, runtime shells), and add new spritesheet/parser files and tests. Also add media/logo.png. These changes align repository code and documentation with the VectorEngine image API contract and update generation/upload handling (green-screen -> alpha processing, spritesheet handling, and related tests).
2026-05-22 03:06:41 +08:00

220 lines
7.1 KiB
Rust

use std::io;
use opentelemetry::{KeyValue, global, trace::TracerProvider};
use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge;
use opentelemetry_otlp::WithExportConfig;
use opentelemetry_sdk::{
Resource, logs::SdkLoggerProvider, metrics::SdkMeterProvider, trace::SdkTracerProvider,
};
use tracing::warn;
use tracing_subscriber::{
EnvFilter, Layer, filter::LevelFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt,
};
#[derive(Clone, Copy, Debug, Default)]
pub struct OtelConfig {
pub enabled: bool,
}
// 统一解析工作区日志过滤器,优先环境变量,其次回落到调用方传入的默认值。
pub fn resolve_env_filter(default_filter: &str) -> EnvFilter {
EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new(default_filter))
.unwrap_or_else(|_| EnvFilter::new("info"))
}
// 统一初始化 tracing subscriber,避免各入口重复散落相同配置。
pub fn init_tracing(default_filter: &str, otel_config: OtelConfig) -> Result<(), io::Error> {
let env_filter = resolve_env_filter(default_filter);
let fmt_layer = fmt::layer().with_target(true).with_ansi(false).compact();
if !otel_config.enabled {
return tracing_subscriber::registry()
.with(env_filter)
.with(fmt_layer)
.try_init()
.map_err(|error| io::Error::other(format!("初始化 tracing subscriber 失败:{error}")));
}
let Some(otel) = build_otel_pipeline() else {
return tracing_subscriber::registry()
.with(env_filter)
.with(fmt_layer)
.try_init()
.map_err(|error| io::Error::other(format!("初始化 tracing subscriber 失败:{error}")));
};
tracing_subscriber::registry()
.with(env_filter)
.with(fmt_layer)
.with(
tracing_opentelemetry::layer()
.with_tracer(otel.tracer_provider.tracer("genarrative-api")),
)
.with(OpenTelemetryTracingBridge::new(&otel.logger_provider).with_filter(LevelFilter::INFO))
.try_init()
.map_err(|error| io::Error::other(format!("初始化 tracing subscriber 失败:{error}")))
}
struct OtelPipeline {
tracer_provider: SdkTracerProvider,
_meter_provider: SdkMeterProvider,
logger_provider: SdkLoggerProvider,
}
fn build_otel_pipeline() -> Option<OtelPipeline> {
let resource = Resource::builder()
.with_service_name(read_env_or_default("OTEL_SERVICE_NAME", "genarrative-api"))
.with_attribute(KeyValue::new("service.namespace", "genarrative"))
.build();
let span_exporter = match opentelemetry_otlp::SpanExporter::builder()
.with_http()
.with_endpoint(resolve_otlp_http_signal_endpoint(
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT",
"/v1/traces",
))
.build()
{
Ok(exporter) => exporter,
Err(error) => {
warn!(%error, "OpenTelemetry span exporter 初始化失败,已回退为本地日志");
return None;
}
};
let metric_exporter = match opentelemetry_otlp::MetricExporter::builder()
.with_http()
.with_endpoint(resolve_otlp_http_signal_endpoint(
"OTEL_EXPORTER_OTLP_METRICS_ENDPOINT",
"/v1/metrics",
))
.build()
{
Ok(exporter) => exporter,
Err(error) => {
warn!(%error, "OpenTelemetry metric exporter 初始化失败,已回退为本地日志");
return None;
}
};
let log_exporter = match opentelemetry_otlp::LogExporter::builder()
.with_http()
.with_endpoint(resolve_otlp_http_signal_endpoint(
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT",
"/v1/logs",
))
.build()
{
Ok(exporter) => exporter,
Err(error) => {
warn!(%error, "OpenTelemetry log exporter 初始化失败,已回退为本地日志");
return None;
}
};
let tracer_provider = SdkTracerProvider::builder()
.with_resource(resource.clone())
.with_batch_exporter(span_exporter)
.build();
let meter_provider = SdkMeterProvider::builder()
.with_resource(resource)
.with_periodic_exporter(metric_exporter)
.build();
let logger_provider = SdkLoggerProvider::builder()
.with_resource(
Resource::builder()
.with_service_name(read_env_or_default("OTEL_SERVICE_NAME", "genarrative-api"))
.with_attribute(KeyValue::new("service.namespace", "genarrative"))
.build(),
)
.with_batch_exporter(log_exporter)
.build();
global::set_tracer_provider(tracer_provider.clone());
global::set_meter_provider(meter_provider.clone());
Some(OtelPipeline {
tracer_provider,
_meter_provider: meter_provider,
logger_provider,
})
}
fn read_env_or_default(key: &str, default_value: &str) -> String {
std::env::var(key)
.ok()
.filter(|value| !value.trim().is_empty())
.unwrap_or_else(|| default_value.to_string())
}
fn resolve_otlp_http_signal_endpoint(signal_key: &str, signal_path: &str) -> String {
if let Ok(value) = std::env::var(signal_key)
&& !value.trim().is_empty()
{
return value;
}
append_otlp_signal_path(
&read_env_or_default("OTEL_EXPORTER_OTLP_ENDPOINT", "http://127.0.0.1:4318"),
signal_path,
)
}
fn append_otlp_signal_path(base_endpoint: &str, signal_path: &str) -> String {
let base_endpoint = base_endpoint.trim_end_matches('/');
let signal_path = signal_path.trim_start_matches('/');
format!("{base_endpoint}/{signal_path}")
}
#[cfg(test)]
mod tests {
use std::sync::{Mutex, OnceLock};
use super::resolve_otlp_http_signal_endpoint;
const OTEL_ENDPOINT_ENV_KEYS: [&str; 4] = [
"OTEL_EXPORTER_OTLP_ENDPOINT",
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT",
"OTEL_EXPORTER_OTLP_METRICS_ENDPOINT",
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT",
];
fn env_lock() -> std::sync::MutexGuard<'static, ()> {
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
LOCK.get_or_init(|| Mutex::new(())).lock().unwrap()
}
fn clear_otel_endpoint_env() {
unsafe {
for key in OTEL_ENDPOINT_ENV_KEYS {
std::env::remove_var(key);
}
}
}
#[test]
fn generic_otlp_http_endpoint_expands_to_signal_paths() {
let _guard = env_lock();
clear_otel_endpoint_env();
unsafe {
std::env::set_var("OTEL_EXPORTER_OTLP_ENDPOINT", "http://127.0.0.1:4318");
}
assert_eq!(
resolve_otlp_http_signal_endpoint("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", "/v1/traces"),
"http://127.0.0.1:4318/v1/traces"
);
assert_eq!(
resolve_otlp_http_signal_endpoint("OTEL_EXPORTER_OTLP_METRICS_ENDPOINT", "/v1/metrics"),
"http://127.0.0.1:4318/v1/metrics"
);
assert_eq!(
resolve_otlp_http_signal_endpoint("OTEL_EXPORTER_OTLP_LOGS_ENDPOINT", "/v1/logs"),
"http://127.0.0.1:4318/v1/logs"
);
clear_otel_endpoint_env();
}
}