Skip to content

Logger

@granit/logger provides a structured logging system for browser applications with configurable severity levels, named prefixes, child loggers, and a pluggable transport architecture. @granit/logger-otlp adds an OTLP HTTP transport that sends logs to an OpenTelemetry collector (Grafana Alloy, Aspire Dashboard) with trace correlation — pairing with Granit.Observability on the .NET backend.

  • Directory@granit/logger/ Logger factory, console transport, transport interface
    • @granit/logger-otlp OTLP HTTP transport for OpenTelemetry collectors
PackageRoleDepends on
@granit/loggercreateLogger(), createConsoleTransport(), LogLevel— (no peer deps)
@granit/logger-otlpcreateOtlpTransport() for OTLP HTTP log export@granit/logger
import { createLogger } from '@granit/logger';
const logger = createLogger('MyApp');
logger.info('Application started');
logger.error('Something failed', new Error('connection refused'));

No configuration needed. Defaults to createConsoleTransport() with colored DevTools output.

Creates a logger instance with a named prefix and configurable transports.

interface LoggerOptions {
level?: LogLevelName; // default: auto-detected from Vite environment
transports?: LogTransport[]; // default: [createConsoleTransport()]
}
function createLogger(prefix: string, options?: LoggerOptions): Logger;

Level auto-detection: defaults to 'DEBUG' in Vite dev mode (import.meta.env.DEV), 'WARN' in production builds.

interface Logger {
debug(message: string, context?: LogContext): void;
info(message: string, context?: LogContext): void;
warn(message: string, context?: LogContext): void;
error(message: string, error?: unknown, context?: LogContext): void;
child(subPrefix: string): Logger;
}
type LogContext = Record<string, unknown>;

Child loggers inherit the parent’s level and transports. Prefixes are concatenated:

const auth = createLogger('Auth');
const token = auth.child('Token');
token.info('Refreshed'); // "[Auth] [Token] Refreshed"
const LogLevel = {
DEBUG: 0,
INFO: 1,
WARN: 2,
ERROR: 3,
} as const;
type LogLevelName = 'DEBUG' | 'INFO' | 'WARN' | 'ERROR';
type LogLevelValue = 0 | 1 | 2 | 3;
LevelConsole methodDevTools badge color
DEBUGconsole.logGrey #71717a
INFOconsole.infoBlue #0ea5e9
WARNconsole.warnAmber #f59e0b
ERRORconsole.errorRed #ef4444
interface LogTransport {
send(entry: LogEntry): void;
flush?(): Promise<void>;
}
interface LogEntry {
timestamp: number; // Unix ms (Date.now())
level: LogLevelName;
prefix: string;
message: string;
error?: unknown;
context?: LogContext;
}

Any transport with a flush() method is automatically registered for a shared beforeunload event listener to ensure logs are delivered before the page closes.

Built-in transport that outputs colored, prefixed messages to the browser DevTools console.

function createConsoleTransport(): LogTransport;

@granit/logger-otlp sends logs as OTLP JSON to an OpenTelemetry-compatible collector. Logs are batched and flushed periodically for efficiency.

interface OtlpTransportOptions {
endpoint: string; // e.g. '/v1/logs' or full URL
serviceName: string; // e.g. 'guava-front'
serviceVersion?: string;
environment?: string; // e.g. 'production'
headers?: Record<string, string>; // additional HTTP headers
batchSize?: number; // auto-flush threshold, default: 10
flushInterval?: number; // ms between flushes, default: 5_000
getTraceContext?: () => TraceContext | undefined; // log-to-trace correlation
}
interface TraceContext {
traceId: string;
spanId: string;
}
function createOtlpTransport(options: OtlpTransportOptions): LogTransport;

OTLP mapping:

  • Log levels map to OTLP severity numbers: DEBUG=5, INFO=9, WARN=13, ERROR=17
  • Resource attributes: service.name, service.version, deployment.environment
  • Log attributes: logger.prefix, context key-values, and exception.* for Error objects
  • Uses fetch with keepalive: true for reliable delivery on page unload
  • Self-disables on first network or HTTP error to avoid log spam

Route OTLP requests to the Aspire Dashboard (or local collector) via Vite’s dev server proxy:

vite.config.ts
export default defineConfig({
server: {
proxy: {
'/v1/logs': 'http://localhost:18889',
},
},
});

Pass getTraceContext from @granit/tracing to correlate logs with distributed traces in Grafana (Loki → Tempo):

import { getTraceContext } from '@granit/tracing';
createOtlpTransport({
endpoint: '/v1/logs',
serviceName: 'guava-front',
getTraceContext,
});

Each log entry includes traceId and spanId from the active OpenTelemetry span, enabling direct navigation from a log line to its trace in Grafana Tempo.

CategoryKey exportsPackage
FactorycreateLogger()@granit/logger
TransportcreateConsoleTransport(), LogTransport@granit/logger
OTLPcreateOtlpTransport(), OtlpTransportOptions, TraceContext@granit/logger-otlp
TypesLogger, LogEntry, LogContext, LoggerOptions@granit/logger
ConstantsLogLevel, LogLevelName, LogLevelValue@granit/logger