Appearance
@carrot/logging โ
ts
Structured logging abstraction with pluggable sinks. Mirrors the shape of .NET's Carrot.Logging - fluent configuration, severity filtering per sink, contextual property scoping, and correlation ids.
Installation โ
ts
import { Log, consoleSink } from '@carrot/logging';Zero dependencies. Browser and Node compatible.
Architecture โ
Fluent Configuration โ
Follows the .NET builder pattern - Log.configure() returns a LogConfiguration that collects sinks and settings, then build() locks it and produces a Log instance:
ts
const log = Log.configure()
.minLevel('debug')
.addSink(consoleSink(), 'info')
.addSink(appInsightsSink({ connectionString: '...' }), 'warn')
.if(isDev, c => c.addSink(consoleSink({ colour: true }), 'debug'))
.build();Each sink gets its own minimum severity gate, independent of the logger's global minimum.
Log Modules โ
Every severity level is a LogModule - a callable function that doubles as an object:
ts
log.info('message'); // callable
log.info('message', { userId: 42 }); // with properties
log.info.write({ message: '...', error }); // full options
log.info.enabled = false; // toggle at runtimeCustom modules add a label tag so sinks can distinguish [Audit] from a plain info:
ts
const audit = log.addModule('audit', 'info');
audit.write('User changed password');Context Stack โ
LogContext manages a synchronous stack of property bags. Every active scope's properties are merged into each LogEvent at emit time:
ts
const scope = log.context.push({ requestId: 'abc-123' });
log.info('Processing'); // event.properties includes requestId
scope.dispose();Supports using via Symbol.dispose and callback-scoped variants (run, runAsync).
Correlation IDs โ
Every write() call returns a unique id (YYYYMMDDHHmmss + 8 hex). Nested writes can reference a parent id for tracing call chains:
ts
const id = log.info('Started batch');
log.debug.writeNested(id, 'Processing item 1');Sink Interface โ
Sinks are intentionally minimal:
ts
interface LogSink {
emit(event: LogEvent): void;
flush?(): void | Promise<void>;
}The emit method receives a fully-resolved LogEvent with merged context properties, timestamp, and correlation id. Sinks that buffer (e.g. HTTP batch sinks) implement flush().
Sink errors are swallowed - a sink must never bring down the app.
File Structure โ
src/
โโโ index.ts # Public API barrel
โโโ types.ts # LogLevel, LogEvent, LogWriteInput
โโโ log.ts # Log class - owns sinks, context, modules
โโโ configuration.ts # LogConfiguration fluent builder
โโโ context.ts # LogContext property scope stack
โโโ module.ts # LogModule factory and emit logic
โโโ id.ts # Correlation ID generation
โโโ sink.ts # LogSink interface + SinkRegistration
โโโ sinks/
โโโ console.ts # Built-in console sinkDependencies โ
None.
Build โ
bash
npm run buildCompiles TypeScript to ESM via tsc. Output lands in dist/.
Using @carrot/logging โ
Structured logging with pluggable sinks, scoped context, and per-sink severity filtering.
Import โ
ts
import { Log, consoleSink } from '@carrot/logging';Common Patterns โ
1. Quick console logger โ
ts
const log = Log.console(); // info+ to console
const log = Log.console('debug'); // debug+ to console2. Multi-sink setup โ
ts
import { appInsightsSink } from '@carrot/logging-applicationinsights';
const log = Log.configure()
.minLevel('debug')
.addSink(consoleSink(), 'debug')
.addSink(appInsightsSink({ connectionString: AI_CONN }), 'warn')
.build();Each sink filters independently - console gets everything from debug up, App Insights only gets warnings and above.
3. Writing log entries โ
ts
log.info('User signed in');
log.warn('Retry attempt', { attempt: 3, endpoint: '/api/data' });
log.error.write({ message: 'Request failed', error: err, properties: { url } });Every level module (verbose, debug, info, warn, error, fatal) is both callable and has a .write() method.
4. Scoped context โ
Push properties that automatically appear on every event while the scope is active:
ts
const scope = log.context.push({ requestId: 'abc-123', userId: 42 });
log.info('Processing request'); // includes requestId + userId
log.debug('Fetching data'); // includes requestId + userId
scope.dispose(); // properties removedWith using (explicit resource management):
ts
{
using scope = log.context.push({ requestId: 'abc-123' });
log.info('Scoped log entry');
}
// scope auto-disposedCallback-scoped:
ts
log.context.run({ batchId: 'xyz' }, () => {
log.info('Inside batch'); // includes batchId
});5. Correlation IDs โ
Every write returns a unique id. Use it to link related entries:
ts
const batchId = log.info('Batch started');
for (const item of items) {
log.debug.writeNested(batchId, `Processing ${item.name}`);
}6. Custom modules โ
Tag log entries with a label for filtering in sinks:
ts
const audit = log.addModule('audit', 'info');
audit('User changed password'); // tagged [audit] at info severity
const perf = log.addModule('perf', 'debug');
perf('Render took 12ms'); // tagged [perf] at debug severity7. Conditional configuration โ
ts
const log = Log.configure()
.minLevel('info')
.addSink(appInsightsSink({ connectionString: AI_CONN }))
.if(import.meta.env.DEV, c => c.addSink(consoleSink(), 'debug'))
.build();8. Toggling modules at runtime โ
ts
log.verbose.enabled = false; // suppress verbose output
log.debug.enabled = false; // suppress debug output9. Flushing before exit โ
ts
await log.flush(); // waits for all sinks that bufferConsole Sink Options โ
ts
consoleSink({
timestamp: true, // [HH:mm:ss] prefix
properties: true, // structured properties in output
colour: true, // use console.info/warn/error for colour
});Tips โ
- Start with
Log.console()during development, wire up real sinks later. - Use context scopes for request/session tracing - avoids passing ids through every call.
- Set per-sink levels to reduce noise in production sinks while keeping verbose output locally.
- Always
flush()before process exit if you have buffered sinks (e.g. App Insights).
Package Contents โ
@carrot/logging-applicationinsights โ
Import: import { ... } from '@carrot/logging-applicationinsights';
Factory โ
appInsightsSink(options) - creates a LogSink & AppInsightsSinkAccessor
Options โ
| Field | Type | Description |
|---|---|---|
connectionString | string? | AI connection string. Required unless instance is provided |
instance | ApplicationInsights? | Bring-your-own AI instance - skips creation |
config | Partial<IConfiguration & IConfig>? | Additional AI SDK config (merged with defaults) |
baseProperties | Record<string, unknown>? | Static properties attached to every telemetry item |
Accessor โ
AppInsightsSinkAccessor - { readonly appInsights: ApplicationInsights }
Exposes the underlying AI instance for custom events, metrics, or page views without creating a second instance.
Re-exports โ
ApplicationInsights ยท SeverityLevel - re-exported types from @microsoft/applicationinsights-web
@carrot/logging-vue โ
Import: import { ... } from '@carrot/logging-vue';
Plugin โ
carrotLogging - Vue 3 plugin (app.use(carrotLogging, options))
CarrotLoggingPluginOptions:
| Field | Type | Default | Description |
|---|---|---|---|
log | Log | required | The logger instance to provide |
router | Router? | - | If provided, binds navigation logging |
routerOptions | RouterBindingOptions? | - | Options forwarded to bindLogToRouter |
captureErrors | boolean | true | Wire app.config.errorHandler to log unhandled errors |
captureWarnings | boolean | true | Wire app.config.warnHandler to log Vue warnings |
Composable โ
useLog() - returns the Log instance from injection context. Throws if the plugin isn't installed.
LogKey - InjectionKey<Log> symbol for manual provide/inject.
Router Binding โ
bindLogToRouter(log, router, options?) - logs an info entry on every navigation. Returns a teardown function.
RouterBindingOptions:
| Field | Type | Default | Description |
|---|---|---|---|
includeQuery | boolean | false | Include route query params (disabled by default to avoid leaking sensitive data) |
includeParams | boolean | true | Include route params |
includeMeta | boolean | true | Include route meta |