feat(server/otel): integrate OpenTelemetry logging and emit logs with context

This commit is contained in:
RainbowBird
2026-03-28 03:59:09 +08:00
parent 04c8f499b9
commit 45bc5fef22
4 changed files with 49 additions and 3 deletions
+1
View File
@@ -24,6 +24,7 @@
"@moeru/eventa": "catalog:",
"@moeru/std": "catalog:",
"@opentelemetry/api": "^1.9.1",
"@opentelemetry/api-logs": "^0.214.0",
"@opentelemetry/exporter-logs-otlp-proto": "^0.214.0",
"@opentelemetry/exporter-metrics-otlp-proto": "^0.214.0",
"@opentelemetry/exporter-trace-otlp-proto": "^0.214.0",
+7 -2
View File
@@ -16,7 +16,7 @@ import type { HonoEnv } from './types/hono'
import process from 'node:process'
import { initLogger, LoggerFormat, LoggerLevel, useLogger } from '@guiiai/logg'
import { initLogger, LoggerFormat, LoggerLevel, setGlobalHookPostLog, useLogger } from '@guiiai/logg'
import { serve } from '@hono/node-server'
import { createNodeWebSocket } from '@hono/node-ws'
import { Hono } from 'hono'
@@ -29,7 +29,7 @@ import { createAuth } from './libs/auth'
import { createDrizzle, migrateDatabase } from './libs/db'
import { parsedEnv } from './libs/env'
import { initializeExternalDependency } from './libs/external-dependency'
import { initOtel } from './libs/otel'
import { emitOtelLog, initOtel } from './libs/otel'
import { createRedis } from './libs/redis'
import { sessionMiddleware } from './middlewares/auth'
import { otelMiddleware } from './middlewares/otel'
@@ -190,6 +190,11 @@ export async function createApp() {
injeca.setLogger(createLoggLogger(useLogger('injeca').useGlobalConfig()))
const logger = useLogger('app').useGlobalConfig()
// Forward logg output to OpenTelemetry log exporter
setGlobalHookPostLog((log) => {
emitOtelLog(log.level, log.context, log.message, log.fields as Record<string, string | number | boolean>)
})
const otel = injeca.provide('libs:otel', {
dependsOn: { env: parsedEnv, lifecycle },
build: ({ dependsOn }) => {
+38 -1
View File
@@ -7,7 +7,8 @@ import type { Env } from './env'
import { env as processEnv } from 'node:process'
import { useLogger } from '@guiiai/logg'
import { diag, DiagConsoleLogger, DiagLogLevel, metrics } from '@opentelemetry/api'
import { diag, DiagConsoleLogger, DiagLogLevel, metrics, trace } from '@opentelemetry/api'
import { logs, SeverityNumber } from '@opentelemetry/api-logs'
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-proto'
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-proto'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto'
@@ -298,3 +299,39 @@ export function initOtel(env: Env): OtelInstance | undefined {
shutdown,
}
}
const severityMap: Record<string, SeverityNumber> = {
debug: SeverityNumber.DEBUG,
verbose: SeverityNumber.TRACE,
log: SeverityNumber.INFO,
info: SeverityNumber.INFO,
warn: SeverityNumber.WARN,
error: SeverityNumber.ERROR,
}
/**
* Emit a log record to OpenTelemetry.
* Automatically attaches the active span's traceId/spanId when available.
*/
export function emitOtelLog(
level: string,
context: string,
message: string,
attributes?: Record<string, string | number | boolean>,
): void {
const otelLogger = logs.getLogger(context)
const spanContext = trace.getActiveSpan()?.spanContext()
otelLogger.emit({
severityNumber: severityMap[level.toLowerCase()] ?? SeverityNumber.INFO,
severityText: level.toUpperCase(),
body: message,
attributes: {
...attributes,
...(spanContext && {
trace_id: spanContext.traceId,
span_id: spanContext.spanId,
}),
},
})
}
+3
View File
@@ -530,6 +530,9 @@ importers:
'@opentelemetry/api':
specifier: ^1.9.1
version: 1.9.1
'@opentelemetry/api-logs':
specifier: ^0.214.0
version: 0.214.0
'@opentelemetry/exporter-logs-otlp-proto':
specifier: ^0.214.0
version: 0.214.0(@opentelemetry/api@1.9.1)