Skip to content

Commit b81dab2

Browse files
committed
fix(rivetkit): align logs with engine logfmt
1 parent a9e01d7 commit b81dab2

4 files changed

Lines changed: 159 additions & 18 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rivetkit-typescript/packages/rivetkit-napi/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ anyhow.workspace = true
2020
serde.workspace = true
2121
serde_json.workspace = true
2222
tracing.workspace = true
23+
tracing-logfmt.workspace = true
24+
tracing-stackdriver.workspace = true
2325
tracing-subscriber.workspace = true
2426
parking_lot.workspace = true
2527
scc.workspace = true

rivetkit-typescript/packages/rivetkit-napi/src/lib.rs

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,30 @@ use std::sync::Once;
1515

1616
use rivet_error::RivetError as RivetTransportError;
1717
use rivetkit_core::error::public_error_status_code;
18+
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
1819

1920
static INIT_TRACING: Once = Once::new();
2021
pub(crate) const BRIDGE_RIVET_ERROR_PREFIX: &str = "__RIVET_ERROR_JSON__:";
2122

23+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24+
enum LogFormat {
25+
Logfmt,
26+
Gcp,
27+
}
28+
29+
impl LogFormat {
30+
fn from_env() -> Self {
31+
match std::env::var("RUST_LOG_FORMAT")
32+
.unwrap_or_default()
33+
.to_lowercase()
34+
.as_str()
35+
{
36+
"gcp" => LogFormat::Gcp,
37+
_ => LogFormat::Logfmt,
38+
}
39+
}
40+
}
41+
2242
#[derive(rivet_error::RivetError, serde::Serialize)]
2343
#[error(
2444
"napi",
@@ -105,13 +125,34 @@ pub(crate) fn init_tracing(log_level: Option<&str>) {
105125
.or_else(|| std::env::var("RUST_LOG").ok())
106126
.unwrap_or_else(|| "warn".to_string());
107127

108-
tracing_subscriber::fmt()
109-
.json()
110-
.with_env_filter(tracing_subscriber::EnvFilter::new(&filter))
111-
.with_target(true)
112-
.with_current_span(true)
113-
.with_span_list(false)
114-
.with_writer(std::io::stdout)
128+
let log_format = LogFormat::from_env();
129+
130+
tracing_subscriber::registry()
131+
.with(tracing_subscriber::EnvFilter::new(&filter))
132+
.with(match log_format {
133+
LogFormat::Logfmt => Some(
134+
tracing_logfmt::builder()
135+
.with_span_name(env_flag("RUST_LOG_SPAN_NAME"))
136+
.with_span_path(env_flag("RUST_LOG_SPAN_PATH"))
137+
.with_target(env_flag("RUST_LOG_TARGET") || env_flag("RIVET_LOG_TARGET"))
138+
.with_location(env_flag("RUST_LOG_LOCATION"))
139+
.with_module_path(env_flag("RUST_LOG_MODULE_PATH"))
140+
.with_ansi_color(env_flag("RUST_LOG_ANSI_COLOR"))
141+
.layer(),
142+
),
143+
LogFormat::Gcp => None,
144+
})
145+
.with(match log_format {
146+
LogFormat::Logfmt => None,
147+
LogFormat::Gcp => Some(
148+
tracing_stackdriver::layer()
149+
.with_source_location(env_flag("RUST_LOG_LOCATION")),
150+
),
151+
})
115152
.init();
116153
});
117154
}
155+
156+
fn env_flag(name: &str) -> bool {
157+
std::env::var(name).map_or(false, |x| x == "1")
158+
}

rivetkit-typescript/packages/rivetkit/src/common/log.ts

Lines changed: 107 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
type DestinationStream,
23
type LevelWithSilent,
34
type Logger,
45
pino,
@@ -69,19 +70,22 @@ export function configureDefaultLogger(logLevel?: LogLevel) {
6970
configuredLogLevel = logLevel;
7071
}
7172

72-
baseLogger = pino({
73-
level: getPinoLevel(logLevel),
74-
messageKey: "msg",
75-
// Do not include pid/hostname in output
76-
base: {},
77-
// Keep a string level in the output
78-
formatters: {
79-
level(_label: string, number: number) {
80-
return { level: number };
73+
baseLogger = pino(
74+
{
75+
level: getPinoLevel(logLevel),
76+
messageKey: "msg",
77+
// Do not include pid/hostname in output
78+
base: {},
79+
// Keep the numeric level so the logfmt sink can match Pino's levels.
80+
formatters: {
81+
level(_label: string, number: number) {
82+
return { level: number };
83+
},
8184
},
85+
timestamp: getLogTimestamp() ? stdTimeFunctions.epochTime : false,
8286
},
83-
timestamp: getLogTimestamp() ? stdTimeFunctions.epochTime : false,
84-
});
87+
createLogfmtDestination(),
88+
);
8589

8690
loggerCache.clear();
8791
}
@@ -117,3 +121,95 @@ export function getLogger(name = "default"): Logger {
117121

118122
return child;
119123
}
124+
125+
const PINO_LEVEL_LABELS: Record<number, string> = {
126+
10: "trace",
127+
20: "debug",
128+
30: "info",
129+
40: "warn",
130+
50: "error",
131+
60: "fatal",
132+
};
133+
134+
function createLogfmtDestination(): DestinationStream {
135+
return {
136+
write(msg: string): void {
137+
const line = formatLogfmtLine(msg);
138+
if (typeof process !== "undefined" && process.stdout?.write) {
139+
process.stdout.write(`${line}\n`);
140+
} else {
141+
console.log(line);
142+
}
143+
},
144+
};
145+
}
146+
147+
function formatLogfmtLine(raw: string): string {
148+
let data: Record<string, unknown>;
149+
try {
150+
data = JSON.parse(raw);
151+
} catch {
152+
return raw.trimEnd();
153+
}
154+
155+
const parts: string[] = [];
156+
appendLogfmtEntry(parts, "level", formatPinoLevel(data.level));
157+
158+
if (data.time !== undefined) {
159+
appendLogfmtEntry(parts, "ts", data.time);
160+
}
161+
162+
for (const [key, value] of Object.entries(data)) {
163+
if (key === "level" || key === "time") {
164+
continue;
165+
}
166+
appendLogfmtEntry(parts, key, value);
167+
}
168+
169+
return parts.join(" ");
170+
}
171+
172+
function formatPinoLevel(level: unknown): string {
173+
if (typeof level === "number") {
174+
return PINO_LEVEL_LABELS[level] ?? level.toString();
175+
}
176+
177+
if (typeof level === "string") {
178+
return level.toLowerCase();
179+
}
180+
181+
return "info";
182+
}
183+
184+
function appendLogfmtEntry(parts: string[], key: string, value: unknown): void {
185+
const safeKey = key.replace(/[\s="]/g, "");
186+
if (safeKey.length === 0) {
187+
return;
188+
}
189+
190+
parts.push(`${safeKey}=${formatLogfmtValue(value)}`);
191+
}
192+
193+
function formatLogfmtValue(value: unknown): string {
194+
if (typeof value === "number" || typeof value === "boolean") {
195+
return String(value);
196+
}
197+
198+
if (value === null || value === undefined) {
199+
return "null";
200+
}
201+
202+
if (typeof value === "string") {
203+
return quoteLogfmtString(value);
204+
}
205+
206+
return quoteLogfmtString(JSON.stringify(value));
207+
}
208+
209+
function quoteLogfmtString(value: string): string {
210+
if (!/[\s="]/.test(value)) {
211+
return value;
212+
}
213+
214+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n")}"`;
215+
}

0 commit comments

Comments
 (0)